mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
feat(server/web) Add manual job trigger mechanism to the web (#767)
This commit is contained in:
parent
854c214bc0
commit
7587f858ae
@ -8,6 +8,7 @@ doc/AdminSignupResponseDto.md
|
|||||||
doc/AlbumApi.md
|
doc/AlbumApi.md
|
||||||
doc/AlbumCountResponseDto.md
|
doc/AlbumCountResponseDto.md
|
||||||
doc/AlbumResponseDto.md
|
doc/AlbumResponseDto.md
|
||||||
|
doc/AllJobStatusResponseDto.md
|
||||||
doc/AssetApi.md
|
doc/AssetApi.md
|
||||||
doc/AssetCountByTimeBucket.md
|
doc/AssetCountByTimeBucket.md
|
||||||
doc/AssetCountByTimeBucketResponseDto.md
|
doc/AssetCountByTimeBucketResponseDto.md
|
||||||
@ -33,6 +34,12 @@ doc/DeviceTypeEnum.md
|
|||||||
doc/ExifResponseDto.md
|
doc/ExifResponseDto.md
|
||||||
doc/GetAssetByTimeBucketDto.md
|
doc/GetAssetByTimeBucketDto.md
|
||||||
doc/GetAssetCountByTimeBucketDto.md
|
doc/GetAssetCountByTimeBucketDto.md
|
||||||
|
doc/JobApi.md
|
||||||
|
doc/JobCommand.md
|
||||||
|
doc/JobCommandDto.md
|
||||||
|
doc/JobCounts.md
|
||||||
|
doc/JobId.md
|
||||||
|
doc/JobStatusResponseDto.md
|
||||||
doc/LoginCredentialDto.md
|
doc/LoginCredentialDto.md
|
||||||
doc/LoginResponseDto.md
|
doc/LoginResponseDto.md
|
||||||
doc/LogoutResponseDto.md
|
doc/LogoutResponseDto.md
|
||||||
@ -59,6 +66,7 @@ lib/api/album_api.dart
|
|||||||
lib/api/asset_api.dart
|
lib/api/asset_api.dart
|
||||||
lib/api/authentication_api.dart
|
lib/api/authentication_api.dart
|
||||||
lib/api/device_info_api.dart
|
lib/api/device_info_api.dart
|
||||||
|
lib/api/job_api.dart
|
||||||
lib/api/server_info_api.dart
|
lib/api/server_info_api.dart
|
||||||
lib/api/user_api.dart
|
lib/api/user_api.dart
|
||||||
lib/api_client.dart
|
lib/api_client.dart
|
||||||
@ -74,6 +82,7 @@ lib/model/add_users_dto.dart
|
|||||||
lib/model/admin_signup_response_dto.dart
|
lib/model/admin_signup_response_dto.dart
|
||||||
lib/model/album_count_response_dto.dart
|
lib/model/album_count_response_dto.dart
|
||||||
lib/model/album_response_dto.dart
|
lib/model/album_response_dto.dart
|
||||||
|
lib/model/all_job_status_response_dto.dart
|
||||||
lib/model/asset_count_by_time_bucket.dart
|
lib/model/asset_count_by_time_bucket.dart
|
||||||
lib/model/asset_count_by_time_bucket_response_dto.dart
|
lib/model/asset_count_by_time_bucket_response_dto.dart
|
||||||
lib/model/asset_count_by_user_id_response_dto.dart
|
lib/model/asset_count_by_user_id_response_dto.dart
|
||||||
@ -96,6 +105,11 @@ lib/model/device_type_enum.dart
|
|||||||
lib/model/exif_response_dto.dart
|
lib/model/exif_response_dto.dart
|
||||||
lib/model/get_asset_by_time_bucket_dto.dart
|
lib/model/get_asset_by_time_bucket_dto.dart
|
||||||
lib/model/get_asset_count_by_time_bucket_dto.dart
|
lib/model/get_asset_count_by_time_bucket_dto.dart
|
||||||
|
lib/model/job_command.dart
|
||||||
|
lib/model/job_command_dto.dart
|
||||||
|
lib/model/job_counts.dart
|
||||||
|
lib/model/job_id.dart
|
||||||
|
lib/model/job_status_response_dto.dart
|
||||||
lib/model/login_credential_dto.dart
|
lib/model/login_credential_dto.dart
|
||||||
lib/model/login_response_dto.dart
|
lib/model/login_response_dto.dart
|
||||||
lib/model/logout_response_dto.dart
|
lib/model/logout_response_dto.dart
|
||||||
|
@ -97,6 +97,9 @@ Class | Method | HTTP request | Description
|
|||||||
*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |
|
*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |
|
||||||
*DeviceInfoApi* | [**createDeviceInfo**](doc//DeviceInfoApi.md#createdeviceinfo) | **POST** /device-info |
|
*DeviceInfoApi* | [**createDeviceInfo**](doc//DeviceInfoApi.md#createdeviceinfo) | **POST** /device-info |
|
||||||
*DeviceInfoApi* | [**updateDeviceInfo**](doc//DeviceInfoApi.md#updatedeviceinfo) | **PATCH** /device-info |
|
*DeviceInfoApi* | [**updateDeviceInfo**](doc//DeviceInfoApi.md#updatedeviceinfo) | **PATCH** /device-info |
|
||||||
|
*JobApi* | [**getAllJobsStatus**](doc//JobApi.md#getalljobsstatus) | **GET** /jobs |
|
||||||
|
*JobApi* | [**getJobStatus**](doc//JobApi.md#getjobstatus) | **GET** /jobs/{jobId} |
|
||||||
|
*JobApi* | [**sendJobCommand**](doc//JobApi.md#sendjobcommand) | **PUT** /jobs/{jobId} |
|
||||||
*ServerInfoApi* | [**getServerInfo**](doc//ServerInfoApi.md#getserverinfo) | **GET** /server-info |
|
*ServerInfoApi* | [**getServerInfo**](doc//ServerInfoApi.md#getserverinfo) | **GET** /server-info |
|
||||||
*ServerInfoApi* | [**getServerVersion**](doc//ServerInfoApi.md#getserverversion) | **GET** /server-info/version |
|
*ServerInfoApi* | [**getServerVersion**](doc//ServerInfoApi.md#getserverversion) | **GET** /server-info/version |
|
||||||
*ServerInfoApi* | [**pingServer**](doc//ServerInfoApi.md#pingserver) | **GET** /server-info/ping |
|
*ServerInfoApi* | [**pingServer**](doc//ServerInfoApi.md#pingserver) | **GET** /server-info/ping |
|
||||||
@ -117,6 +120,7 @@ Class | Method | HTTP request | Description
|
|||||||
- [AdminSignupResponseDto](doc//AdminSignupResponseDto.md)
|
- [AdminSignupResponseDto](doc//AdminSignupResponseDto.md)
|
||||||
- [AlbumCountResponseDto](doc//AlbumCountResponseDto.md)
|
- [AlbumCountResponseDto](doc//AlbumCountResponseDto.md)
|
||||||
- [AlbumResponseDto](doc//AlbumResponseDto.md)
|
- [AlbumResponseDto](doc//AlbumResponseDto.md)
|
||||||
|
- [AllJobStatusResponseDto](doc//AllJobStatusResponseDto.md)
|
||||||
- [AssetCountByTimeBucket](doc//AssetCountByTimeBucket.md)
|
- [AssetCountByTimeBucket](doc//AssetCountByTimeBucket.md)
|
||||||
- [AssetCountByTimeBucketResponseDto](doc//AssetCountByTimeBucketResponseDto.md)
|
- [AssetCountByTimeBucketResponseDto](doc//AssetCountByTimeBucketResponseDto.md)
|
||||||
- [AssetCountByUserIdResponseDto](doc//AssetCountByUserIdResponseDto.md)
|
- [AssetCountByUserIdResponseDto](doc//AssetCountByUserIdResponseDto.md)
|
||||||
@ -139,6 +143,11 @@ Class | Method | HTTP request | Description
|
|||||||
- [ExifResponseDto](doc//ExifResponseDto.md)
|
- [ExifResponseDto](doc//ExifResponseDto.md)
|
||||||
- [GetAssetByTimeBucketDto](doc//GetAssetByTimeBucketDto.md)
|
- [GetAssetByTimeBucketDto](doc//GetAssetByTimeBucketDto.md)
|
||||||
- [GetAssetCountByTimeBucketDto](doc//GetAssetCountByTimeBucketDto.md)
|
- [GetAssetCountByTimeBucketDto](doc//GetAssetCountByTimeBucketDto.md)
|
||||||
|
- [JobCommand](doc//JobCommand.md)
|
||||||
|
- [JobCommandDto](doc//JobCommandDto.md)
|
||||||
|
- [JobCounts](doc//JobCounts.md)
|
||||||
|
- [JobId](doc//JobId.md)
|
||||||
|
- [JobStatusResponseDto](doc//JobStatusResponseDto.md)
|
||||||
- [LoginCredentialDto](doc//LoginCredentialDto.md)
|
- [LoginCredentialDto](doc//LoginCredentialDto.md)
|
||||||
- [LoginResponseDto](doc//LoginResponseDto.md)
|
- [LoginResponseDto](doc//LoginResponseDto.md)
|
||||||
- [LogoutResponseDto](doc//LogoutResponseDto.md)
|
- [LogoutResponseDto](doc//LogoutResponseDto.md)
|
||||||
|
22
mobile/openapi/doc/AllJobStatusResponseDto.md
Normal file
22
mobile/openapi/doc/AllJobStatusResponseDto.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# openapi.model.AllJobStatusResponseDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**thumbnailGenerationQueueCount** | [**JobCounts**](JobCounts.md) | |
|
||||||
|
**metadataExtractionQueueCount** | [**JobCounts**](JobCounts.md) | |
|
||||||
|
**videoConversionQueueCount** | [**JobCounts**](JobCounts.md) | |
|
||||||
|
**machineLearningQueueCount** | [**JobCounts**](JobCounts.md) | |
|
||||||
|
**isThumbnailGenerationActive** | **bool** | |
|
||||||
|
**isMetadataExtractionActive** | **bool** | |
|
||||||
|
**isVideoConversionActive** | **bool** | |
|
||||||
|
**isMachineLearningActive** | **bool** | |
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
15
mobile/openapi/doc/CreateJobDto.md
Normal file
15
mobile/openapi/doc/CreateJobDto.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# openapi.model.CreateJobDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**jobType** | [**JobType**](JobType.md) | |
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
@ -8,13 +8,13 @@ import 'package:openapi/api.dart';
|
|||||||
## Properties
|
## Properties
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
**id** | **String** | | [optional]
|
**id** | **int** | | [optional]
|
||||||
|
**fileSizeInByte** | **int** | | [optional]
|
||||||
**make** | **String** | | [optional]
|
**make** | **String** | | [optional]
|
||||||
**model** | **String** | | [optional]
|
**model** | **String** | | [optional]
|
||||||
**imageName** | **String** | | [optional]
|
**imageName** | **String** | | [optional]
|
||||||
**exifImageWidth** | **num** | | [optional]
|
**exifImageWidth** | **num** | | [optional]
|
||||||
**exifImageHeight** | **num** | | [optional]
|
**exifImageHeight** | **num** | | [optional]
|
||||||
**fileSizeInByte** | **num** | | [optional]
|
|
||||||
**orientation** | **String** | | [optional]
|
**orientation** | **String** | | [optional]
|
||||||
**dateTimeOriginal** | [**DateTime**](DateTime.md) | | [optional]
|
**dateTimeOriginal** | [**DateTime**](DateTime.md) | | [optional]
|
||||||
**modifyDate** | [**DateTime**](DateTime.md) | | [optional]
|
**modifyDate** | [**DateTime**](DateTime.md) | | [optional]
|
||||||
|
155
mobile/openapi/doc/JobApi.md
Normal file
155
mobile/openapi/doc/JobApi.md
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
# openapi.api.JobApi
|
||||||
|
|
||||||
|
## Load the API package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
All URIs are relative to */api*
|
||||||
|
|
||||||
|
Method | HTTP request | Description
|
||||||
|
------------- | ------------- | -------------
|
||||||
|
[**getAllJobsStatus**](JobApi.md#getalljobsstatus) | **GET** /jobs |
|
||||||
|
[**getJobStatus**](JobApi.md#getjobstatus) | **GET** /jobs/{jobId} |
|
||||||
|
[**sendJobCommand**](JobApi.md#sendjobcommand) | **PUT** /jobs/{jobId} |
|
||||||
|
|
||||||
|
|
||||||
|
# **getAllJobsStatus**
|
||||||
|
> AllJobStatusResponseDto getAllJobsStatus()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
// 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 = JobApi();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final result = api_instance.getAllJobsStatus();
|
||||||
|
print(result);
|
||||||
|
} catch (e) {
|
||||||
|
print('Exception when calling JobApi->getAllJobsStatus: $e\n');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
This endpoint does not need any parameter.
|
||||||
|
|
||||||
|
### Return type
|
||||||
|
|
||||||
|
[**AllJobStatusResponseDto**](AllJobStatusResponseDto.md)
|
||||||
|
|
||||||
|
### Authorization
|
||||||
|
|
||||||
|
[bearer](../README.md#bearer)
|
||||||
|
|
||||||
|
### HTTP request headers
|
||||||
|
|
||||||
|
- **Content-Type**: Not defined
|
||||||
|
- **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)
|
||||||
|
|
||||||
|
# **getJobStatus**
|
||||||
|
> JobStatusResponseDto getJobStatus(jobId)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
// 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 = JobApi();
|
||||||
|
final jobId = ; // JobId |
|
||||||
|
|
||||||
|
try {
|
||||||
|
final result = api_instance.getJobStatus(jobId);
|
||||||
|
print(result);
|
||||||
|
} catch (e) {
|
||||||
|
print('Exception when calling JobApi->getJobStatus: $e\n');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------- | ------------- | ------------- | -------------
|
||||||
|
**jobId** | [**JobId**](.md)| |
|
||||||
|
|
||||||
|
### Return type
|
||||||
|
|
||||||
|
[**JobStatusResponseDto**](JobStatusResponseDto.md)
|
||||||
|
|
||||||
|
### Authorization
|
||||||
|
|
||||||
|
[bearer](../README.md#bearer)
|
||||||
|
|
||||||
|
### HTTP request headers
|
||||||
|
|
||||||
|
- **Content-Type**: Not defined
|
||||||
|
- **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)
|
||||||
|
|
||||||
|
# **sendJobCommand**
|
||||||
|
> num sendJobCommand(jobId, jobCommandDto)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
// 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 = JobApi();
|
||||||
|
final jobId = ; // JobId |
|
||||||
|
final jobCommandDto = JobCommandDto(); // JobCommandDto |
|
||||||
|
|
||||||
|
try {
|
||||||
|
final result = api_instance.sendJobCommand(jobId, jobCommandDto);
|
||||||
|
print(result);
|
||||||
|
} catch (e) {
|
||||||
|
print('Exception when calling JobApi->sendJobCommand: $e\n');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------- | ------------- | ------------- | -------------
|
||||||
|
**jobId** | [**JobId**](.md)| |
|
||||||
|
**jobCommandDto** | [**JobCommandDto**](JobCommandDto.md)| |
|
||||||
|
|
||||||
|
### Return type
|
||||||
|
|
||||||
|
**num**
|
||||||
|
|
||||||
|
### Authorization
|
||||||
|
|
||||||
|
[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)
|
||||||
|
|
14
mobile/openapi/doc/JobCommand.md
Normal file
14
mobile/openapi/doc/JobCommand.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# openapi.model.JobCommand
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
15
mobile/openapi/doc/JobCommandDto.md
Normal file
15
mobile/openapi/doc/JobCommandDto.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# openapi.model.JobCommandDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**command** | [**JobCommand**](JobCommand.md) | |
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
19
mobile/openapi/doc/JobCounts.md
Normal file
19
mobile/openapi/doc/JobCounts.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# openapi.model.JobCounts
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**active** | **num** | |
|
||||||
|
**completed** | **num** | |
|
||||||
|
**failed** | **num** | |
|
||||||
|
**delayed** | **num** | |
|
||||||
|
**waiting** | **num** | |
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
14
mobile/openapi/doc/JobId.md
Normal file
14
mobile/openapi/doc/JobId.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# openapi.model.JobId
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
16
mobile/openapi/doc/JobStatusResponseDto.md
Normal file
16
mobile/openapi/doc/JobStatusResponseDto.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# openapi.model.JobStatusResponseDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**isActive** | **bool** | |
|
||||||
|
**queueCount** | [**Object**](.md) | |
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
14
mobile/openapi/doc/JobType.md
Normal file
14
mobile/openapi/doc/JobType.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# openapi.model.JobType
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
@ -31,6 +31,7 @@ part 'api/album_api.dart';
|
|||||||
part 'api/asset_api.dart';
|
part 'api/asset_api.dart';
|
||||||
part 'api/authentication_api.dart';
|
part 'api/authentication_api.dart';
|
||||||
part 'api/device_info_api.dart';
|
part 'api/device_info_api.dart';
|
||||||
|
part 'api/job_api.dart';
|
||||||
part 'api/server_info_api.dart';
|
part 'api/server_info_api.dart';
|
||||||
part 'api/user_api.dart';
|
part 'api/user_api.dart';
|
||||||
|
|
||||||
@ -39,6 +40,7 @@ part 'model/add_users_dto.dart';
|
|||||||
part 'model/admin_signup_response_dto.dart';
|
part 'model/admin_signup_response_dto.dart';
|
||||||
part 'model/album_count_response_dto.dart';
|
part 'model/album_count_response_dto.dart';
|
||||||
part 'model/album_response_dto.dart';
|
part 'model/album_response_dto.dart';
|
||||||
|
part 'model/all_job_status_response_dto.dart';
|
||||||
part 'model/asset_count_by_time_bucket.dart';
|
part 'model/asset_count_by_time_bucket.dart';
|
||||||
part 'model/asset_count_by_time_bucket_response_dto.dart';
|
part 'model/asset_count_by_time_bucket_response_dto.dart';
|
||||||
part 'model/asset_count_by_user_id_response_dto.dart';
|
part 'model/asset_count_by_user_id_response_dto.dart';
|
||||||
@ -61,6 +63,11 @@ part 'model/device_type_enum.dart';
|
|||||||
part 'model/exif_response_dto.dart';
|
part 'model/exif_response_dto.dart';
|
||||||
part 'model/get_asset_by_time_bucket_dto.dart';
|
part 'model/get_asset_by_time_bucket_dto.dart';
|
||||||
part 'model/get_asset_count_by_time_bucket_dto.dart';
|
part 'model/get_asset_count_by_time_bucket_dto.dart';
|
||||||
|
part 'model/job_command.dart';
|
||||||
|
part 'model/job_command_dto.dart';
|
||||||
|
part 'model/job_counts.dart';
|
||||||
|
part 'model/job_id.dart';
|
||||||
|
part 'model/job_status_response_dto.dart';
|
||||||
part 'model/login_credential_dto.dart';
|
part 'model/login_credential_dto.dart';
|
||||||
part 'model/login_response_dto.dart';
|
part 'model/login_response_dto.dart';
|
||||||
part 'model/logout_response_dto.dart';
|
part 'model/logout_response_dto.dart';
|
||||||
|
159
mobile/openapi/lib/api/job_api.dart
Normal file
159
mobile/openapi/lib/api/job_api.dart
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
//
|
||||||
|
// 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 JobApi {
|
||||||
|
JobApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
|
||||||
|
|
||||||
|
final ApiClient apiClient;
|
||||||
|
|
||||||
|
/// Performs an HTTP 'GET /jobs' operation and returns the [Response].
|
||||||
|
Future<Response> getAllJobsStatusWithHttpInfo() async {
|
||||||
|
// ignore: prefer_const_declarations
|
||||||
|
final path = r'/jobs';
|
||||||
|
|
||||||
|
// ignore: prefer_final_locals
|
||||||
|
Object? postBody;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>[];
|
||||||
|
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
path,
|
||||||
|
'GET',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<AllJobStatusResponseDto?> getAllJobsStatus() async {
|
||||||
|
final response = await getAllJobsStatusWithHttpInfo();
|
||||||
|
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), 'AllJobStatusResponseDto',) as AllJobStatusResponseDto;
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs an HTTP 'GET /jobs/{jobId}' operation and returns the [Response].
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [JobId] jobId (required):
|
||||||
|
Future<Response> getJobStatusWithHttpInfo(JobId jobId,) async {
|
||||||
|
// ignore: prefer_const_declarations
|
||||||
|
final path = r'/jobs/{jobId}'
|
||||||
|
.replaceAll('{jobId}', jobId.toString());
|
||||||
|
|
||||||
|
// ignore: prefer_final_locals
|
||||||
|
Object? postBody;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>[];
|
||||||
|
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
path,
|
||||||
|
'GET',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [JobId] jobId (required):
|
||||||
|
Future<JobStatusResponseDto?> getJobStatus(JobId jobId,) async {
|
||||||
|
final response = await getJobStatusWithHttpInfo(jobId,);
|
||||||
|
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), 'JobStatusResponseDto',) as JobStatusResponseDto;
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs an HTTP 'PUT /jobs/{jobId}' operation and returns the [Response].
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [JobId] jobId (required):
|
||||||
|
///
|
||||||
|
/// * [JobCommandDto] jobCommandDto (required):
|
||||||
|
Future<Response> sendJobCommandWithHttpInfo(JobId jobId, JobCommandDto jobCommandDto,) async {
|
||||||
|
// ignore: prefer_const_declarations
|
||||||
|
final path = r'/jobs/{jobId}'
|
||||||
|
.replaceAll('{jobId}', jobId.toString());
|
||||||
|
|
||||||
|
// ignore: prefer_final_locals
|
||||||
|
Object? postBody = jobCommandDto;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>['application/json'];
|
||||||
|
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
path,
|
||||||
|
'PUT',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [JobId] jobId (required):
|
||||||
|
///
|
||||||
|
/// * [JobCommandDto] jobCommandDto (required):
|
||||||
|
Future<num?> sendJobCommand(JobId jobId, JobCommandDto jobCommandDto,) async {
|
||||||
|
final response = await sendJobCommandWithHttpInfo(jobId, jobCommandDto,);
|
||||||
|
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), 'num',) as num;
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -202,6 +202,8 @@ class ApiClient {
|
|||||||
return AlbumCountResponseDto.fromJson(value);
|
return AlbumCountResponseDto.fromJson(value);
|
||||||
case 'AlbumResponseDto':
|
case 'AlbumResponseDto':
|
||||||
return AlbumResponseDto.fromJson(value);
|
return AlbumResponseDto.fromJson(value);
|
||||||
|
case 'AllJobStatusResponseDto':
|
||||||
|
return AllJobStatusResponseDto.fromJson(value);
|
||||||
case 'AssetCountByTimeBucket':
|
case 'AssetCountByTimeBucket':
|
||||||
return AssetCountByTimeBucket.fromJson(value);
|
return AssetCountByTimeBucket.fromJson(value);
|
||||||
case 'AssetCountByTimeBucketResponseDto':
|
case 'AssetCountByTimeBucketResponseDto':
|
||||||
@ -246,6 +248,16 @@ class ApiClient {
|
|||||||
return GetAssetByTimeBucketDto.fromJson(value);
|
return GetAssetByTimeBucketDto.fromJson(value);
|
||||||
case 'GetAssetCountByTimeBucketDto':
|
case 'GetAssetCountByTimeBucketDto':
|
||||||
return GetAssetCountByTimeBucketDto.fromJson(value);
|
return GetAssetCountByTimeBucketDto.fromJson(value);
|
||||||
|
case 'JobCommand':
|
||||||
|
return JobCommandTypeTransformer().decode(value);
|
||||||
|
case 'JobCommandDto':
|
||||||
|
return JobCommandDto.fromJson(value);
|
||||||
|
case 'JobCounts':
|
||||||
|
return JobCounts.fromJson(value);
|
||||||
|
case 'JobId':
|
||||||
|
return JobIdTypeTransformer().decode(value);
|
||||||
|
case 'JobStatusResponseDto':
|
||||||
|
return JobStatusResponseDto.fromJson(value);
|
||||||
case 'LoginCredentialDto':
|
case 'LoginCredentialDto':
|
||||||
return LoginCredentialDto.fromJson(value);
|
return LoginCredentialDto.fromJson(value);
|
||||||
case 'LoginResponseDto':
|
case 'LoginResponseDto':
|
||||||
|
@ -64,6 +64,12 @@ String parameterToString(dynamic value) {
|
|||||||
if (value is DeviceTypeEnum) {
|
if (value is DeviceTypeEnum) {
|
||||||
return DeviceTypeEnumTypeTransformer().encode(value).toString();
|
return DeviceTypeEnumTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
if (value is JobCommand) {
|
||||||
|
return JobCommandTypeTransformer().encode(value).toString();
|
||||||
|
}
|
||||||
|
if (value is JobId) {
|
||||||
|
return JobIdTypeTransformer().encode(value).toString();
|
||||||
|
}
|
||||||
if (value is ThumbnailFormat) {
|
if (value is ThumbnailFormat) {
|
||||||
return ThumbnailFormatTypeTransformer().encode(value).toString();
|
return ThumbnailFormatTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
167
mobile/openapi/lib/model/all_job_status_response_dto.dart
Normal file
167
mobile/openapi/lib/model/all_job_status_response_dto.dart
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
//
|
||||||
|
// 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 AllJobStatusResponseDto {
|
||||||
|
/// Returns a new [AllJobStatusResponseDto] instance.
|
||||||
|
AllJobStatusResponseDto({
|
||||||
|
required this.thumbnailGenerationQueueCount,
|
||||||
|
required this.metadataExtractionQueueCount,
|
||||||
|
required this.videoConversionQueueCount,
|
||||||
|
required this.machineLearningQueueCount,
|
||||||
|
required this.isThumbnailGenerationActive,
|
||||||
|
required this.isMetadataExtractionActive,
|
||||||
|
required this.isVideoConversionActive,
|
||||||
|
required this.isMachineLearningActive,
|
||||||
|
});
|
||||||
|
|
||||||
|
JobCounts thumbnailGenerationQueueCount;
|
||||||
|
|
||||||
|
JobCounts metadataExtractionQueueCount;
|
||||||
|
|
||||||
|
JobCounts videoConversionQueueCount;
|
||||||
|
|
||||||
|
JobCounts machineLearningQueueCount;
|
||||||
|
|
||||||
|
bool isThumbnailGenerationActive;
|
||||||
|
|
||||||
|
bool isMetadataExtractionActive;
|
||||||
|
|
||||||
|
bool isVideoConversionActive;
|
||||||
|
|
||||||
|
bool isMachineLearningActive;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is AllJobStatusResponseDto &&
|
||||||
|
other.thumbnailGenerationQueueCount == thumbnailGenerationQueueCount &&
|
||||||
|
other.metadataExtractionQueueCount == metadataExtractionQueueCount &&
|
||||||
|
other.videoConversionQueueCount == videoConversionQueueCount &&
|
||||||
|
other.machineLearningQueueCount == machineLearningQueueCount &&
|
||||||
|
other.isThumbnailGenerationActive == isThumbnailGenerationActive &&
|
||||||
|
other.isMetadataExtractionActive == isMetadataExtractionActive &&
|
||||||
|
other.isVideoConversionActive == isVideoConversionActive &&
|
||||||
|
other.isMachineLearningActive == isMachineLearningActive;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(thumbnailGenerationQueueCount.hashCode) +
|
||||||
|
(metadataExtractionQueueCount.hashCode) +
|
||||||
|
(videoConversionQueueCount.hashCode) +
|
||||||
|
(machineLearningQueueCount.hashCode) +
|
||||||
|
(isThumbnailGenerationActive.hashCode) +
|
||||||
|
(isMetadataExtractionActive.hashCode) +
|
||||||
|
(isVideoConversionActive.hashCode) +
|
||||||
|
(isMachineLearningActive.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AllJobStatusResponseDto[thumbnailGenerationQueueCount=$thumbnailGenerationQueueCount, metadataExtractionQueueCount=$metadataExtractionQueueCount, videoConversionQueueCount=$videoConversionQueueCount, machineLearningQueueCount=$machineLearningQueueCount, isThumbnailGenerationActive=$isThumbnailGenerationActive, isMetadataExtractionActive=$isMetadataExtractionActive, isVideoConversionActive=$isVideoConversionActive, isMachineLearningActive=$isMachineLearningActive]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final _json = <String, dynamic>{};
|
||||||
|
_json[r'thumbnailGenerationQueueCount'] = thumbnailGenerationQueueCount;
|
||||||
|
_json[r'metadataExtractionQueueCount'] = metadataExtractionQueueCount;
|
||||||
|
_json[r'videoConversionQueueCount'] = videoConversionQueueCount;
|
||||||
|
_json[r'machineLearningQueueCount'] = machineLearningQueueCount;
|
||||||
|
_json[r'isThumbnailGenerationActive'] = isThumbnailGenerationActive;
|
||||||
|
_json[r'isMetadataExtractionActive'] = isMetadataExtractionActive;
|
||||||
|
_json[r'isVideoConversionActive'] = isVideoConversionActive;
|
||||||
|
_json[r'isMachineLearningActive'] = isMachineLearningActive;
|
||||||
|
return _json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [AllJobStatusResponseDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static AllJobStatusResponseDto? 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 "AllJobStatusResponseDto[$key]" is missing from JSON.');
|
||||||
|
assert(json[key] != null, 'Required key "AllJobStatusResponseDto[$key]" has a null value in JSON.');
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return AllJobStatusResponseDto(
|
||||||
|
thumbnailGenerationQueueCount: JobCounts.fromJson(json[r'thumbnailGenerationQueueCount'])!,
|
||||||
|
metadataExtractionQueueCount: JobCounts.fromJson(json[r'metadataExtractionQueueCount'])!,
|
||||||
|
videoConversionQueueCount: JobCounts.fromJson(json[r'videoConversionQueueCount'])!,
|
||||||
|
machineLearningQueueCount: JobCounts.fromJson(json[r'machineLearningQueueCount'])!,
|
||||||
|
isThumbnailGenerationActive: mapValueOfType<bool>(json, r'isThumbnailGenerationActive')!,
|
||||||
|
isMetadataExtractionActive: mapValueOfType<bool>(json, r'isMetadataExtractionActive')!,
|
||||||
|
isVideoConversionActive: mapValueOfType<bool>(json, r'isVideoConversionActive')!,
|
||||||
|
isMachineLearningActive: mapValueOfType<bool>(json, r'isMachineLearningActive')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<AllJobStatusResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <AllJobStatusResponseDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = AllJobStatusResponseDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, AllJobStatusResponseDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, AllJobStatusResponseDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AllJobStatusResponseDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of AllJobStatusResponseDto-objects as value to a dart map
|
||||||
|
static Map<String, List<AllJobStatusResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<AllJobStatusResponseDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AllJobStatusResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'thumbnailGenerationQueueCount',
|
||||||
|
'metadataExtractionQueueCount',
|
||||||
|
'videoConversionQueueCount',
|
||||||
|
'machineLearningQueueCount',
|
||||||
|
'isThumbnailGenerationActive',
|
||||||
|
'isMetadataExtractionActive',
|
||||||
|
'isVideoConversionActive',
|
||||||
|
'isMachineLearningActive',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -76,72 +76,69 @@ class AssetResponseDto {
|
|||||||
SmartInfoResponseDto? smartInfo;
|
SmartInfoResponseDto? smartInfo;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto &&
|
||||||
identical(this, other) ||
|
other.type == type &&
|
||||||
other is AssetResponseDto &&
|
other.id == id &&
|
||||||
other.type == type &&
|
other.deviceAssetId == deviceAssetId &&
|
||||||
other.id == id &&
|
other.ownerId == ownerId &&
|
||||||
other.deviceAssetId == deviceAssetId &&
|
other.deviceId == deviceId &&
|
||||||
other.ownerId == ownerId &&
|
other.originalPath == originalPath &&
|
||||||
other.deviceId == deviceId &&
|
other.resizePath == resizePath &&
|
||||||
other.originalPath == originalPath &&
|
other.createdAt == createdAt &&
|
||||||
other.resizePath == resizePath &&
|
other.modifiedAt == modifiedAt &&
|
||||||
other.createdAt == createdAt &&
|
other.isFavorite == isFavorite &&
|
||||||
other.modifiedAt == modifiedAt &&
|
other.mimeType == mimeType &&
|
||||||
other.isFavorite == isFavorite &&
|
other.duration == duration &&
|
||||||
other.mimeType == mimeType &&
|
other.webpPath == webpPath &&
|
||||||
other.duration == duration &&
|
other.encodedVideoPath == encodedVideoPath &&
|
||||||
other.webpPath == webpPath &&
|
other.exifInfo == exifInfo &&
|
||||||
other.encodedVideoPath == encodedVideoPath &&
|
other.smartInfo == smartInfo;
|
||||||
other.exifInfo == exifInfo &&
|
|
||||||
other.smartInfo == smartInfo;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(type.hashCode) +
|
(type.hashCode) +
|
||||||
(id.hashCode) +
|
(id.hashCode) +
|
||||||
(deviceAssetId.hashCode) +
|
(deviceAssetId.hashCode) +
|
||||||
(ownerId.hashCode) +
|
(ownerId.hashCode) +
|
||||||
(deviceId.hashCode) +
|
(deviceId.hashCode) +
|
||||||
(originalPath.hashCode) +
|
(originalPath.hashCode) +
|
||||||
(resizePath == null ? 0 : resizePath!.hashCode) +
|
(resizePath == null ? 0 : resizePath!.hashCode) +
|
||||||
(createdAt.hashCode) +
|
(createdAt.hashCode) +
|
||||||
(modifiedAt.hashCode) +
|
(modifiedAt.hashCode) +
|
||||||
(isFavorite.hashCode) +
|
(isFavorite.hashCode) +
|
||||||
(mimeType == null ? 0 : mimeType!.hashCode) +
|
(mimeType == null ? 0 : mimeType!.hashCode) +
|
||||||
(duration.hashCode) +
|
(duration.hashCode) +
|
||||||
(webpPath == null ? 0 : webpPath!.hashCode) +
|
(webpPath == null ? 0 : webpPath!.hashCode) +
|
||||||
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
|
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
|
||||||
(exifInfo == null ? 0 : exifInfo!.hashCode) +
|
(exifInfo == null ? 0 : exifInfo!.hashCode) +
|
||||||
(smartInfo == null ? 0 : smartInfo!.hashCode);
|
(smartInfo == null ? 0 : smartInfo!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo]';
|
||||||
'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo]';
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final _json = <String, dynamic>{};
|
final _json = <String, dynamic>{};
|
||||||
_json[r'type'] = type;
|
_json[r'type'] = type;
|
||||||
_json[r'id'] = id;
|
_json[r'id'] = id;
|
||||||
_json[r'deviceAssetId'] = deviceAssetId;
|
_json[r'deviceAssetId'] = deviceAssetId;
|
||||||
_json[r'ownerId'] = ownerId;
|
_json[r'ownerId'] = ownerId;
|
||||||
_json[r'deviceId'] = deviceId;
|
_json[r'deviceId'] = deviceId;
|
||||||
_json[r'originalPath'] = originalPath;
|
_json[r'originalPath'] = originalPath;
|
||||||
if (resizePath != null) {
|
if (resizePath != null) {
|
||||||
_json[r'resizePath'] = resizePath;
|
_json[r'resizePath'] = resizePath;
|
||||||
} else {
|
} else {
|
||||||
_json[r'resizePath'] = null;
|
_json[r'resizePath'] = null;
|
||||||
}
|
}
|
||||||
_json[r'createdAt'] = createdAt;
|
_json[r'createdAt'] = createdAt;
|
||||||
_json[r'modifiedAt'] = modifiedAt;
|
_json[r'modifiedAt'] = modifiedAt;
|
||||||
_json[r'isFavorite'] = isFavorite;
|
_json[r'isFavorite'] = isFavorite;
|
||||||
if (mimeType != null) {
|
if (mimeType != null) {
|
||||||
_json[r'mimeType'] = mimeType;
|
_json[r'mimeType'] = mimeType;
|
||||||
} else {
|
} else {
|
||||||
_json[r'mimeType'] = null;
|
_json[r'mimeType'] = null;
|
||||||
}
|
}
|
||||||
_json[r'duration'] = duration;
|
_json[r'duration'] = duration;
|
||||||
if (webpPath != null) {
|
if (webpPath != null) {
|
||||||
_json[r'webpPath'] = webpPath;
|
_json[r'webpPath'] = webpPath;
|
||||||
} else {
|
} else {
|
||||||
@ -175,13 +172,13 @@ class AssetResponseDto {
|
|||||||
// Ensure that the map contains the required keys.
|
// Ensure that the map contains the required keys.
|
||||||
// Note 1: the values aren't checked for validity beyond being non-null.
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
// Note 2: this code is stripped in release mode!
|
// Note 2: this code is stripped in release mode!
|
||||||
// assert(() {
|
assert(() {
|
||||||
// requiredKeys.forEach((key) {
|
requiredKeys.forEach((key) {
|
||||||
// assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.');
|
assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.');
|
||||||
// assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.');
|
assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.');
|
||||||
// });
|
});
|
||||||
// return true;
|
return true;
|
||||||
// }());
|
}());
|
||||||
|
|
||||||
return AssetResponseDto(
|
return AssetResponseDto(
|
||||||
type: AssetTypeEnum.fromJson(json[r'type'])!,
|
type: AssetTypeEnum.fromJson(json[r'type'])!,
|
||||||
@ -205,10 +202,7 @@ class AssetResponseDto {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<AssetResponseDto>? listFromJson(
|
static List<AssetResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
dynamic json, {
|
|
||||||
bool growable = false,
|
|
||||||
}) {
|
|
||||||
final result = <AssetResponseDto>[];
|
final result = <AssetResponseDto>[];
|
||||||
if (json is List && json.isNotEmpty) {
|
if (json is List && json.isNotEmpty) {
|
||||||
for (final row in json) {
|
for (final row in json) {
|
||||||
@ -236,18 +230,12 @@ class AssetResponseDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// maps a json object with a list of AssetResponseDto-objects as value to a dart map
|
// maps a json object with a list of AssetResponseDto-objects as value to a dart map
|
||||||
static Map<String, List<AssetResponseDto>> mapListFromJson(
|
static Map<String, List<AssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
dynamic json, {
|
|
||||||
bool growable = false,
|
|
||||||
}) {
|
|
||||||
final map = <String, List<AssetResponseDto>>{};
|
final map = <String, List<AssetResponseDto>>{};
|
||||||
if (json is Map && json.isNotEmpty) {
|
if (json is Map && json.isNotEmpty) {
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
for (final entry in json.entries) {
|
for (final entry in json.entries) {
|
||||||
final value = AssetResponseDto.listFromJson(
|
final value = AssetResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
entry.value,
|
|
||||||
growable: growable,
|
|
||||||
);
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
map[entry.key] = value;
|
map[entry.key] = value;
|
||||||
}
|
}
|
||||||
@ -274,3 +262,4 @@ class AssetResponseDto {
|
|||||||
'encodedVideoPath',
|
'encodedVideoPath',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
111
mobile/openapi/lib/model/create_job_dto.dart
Normal file
111
mobile/openapi/lib/model/create_job_dto.dart
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
//
|
||||||
|
// 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 CreateJobDto {
|
||||||
|
/// Returns a new [CreateJobDto] instance.
|
||||||
|
CreateJobDto({
|
||||||
|
required this.jobType,
|
||||||
|
});
|
||||||
|
|
||||||
|
JobType jobType;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is CreateJobDto &&
|
||||||
|
other.jobType == jobType;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(jobType.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'CreateJobDto[jobType=$jobType]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final _json = <String, dynamic>{};
|
||||||
|
_json[r'jobType'] = jobType;
|
||||||
|
return _json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [CreateJobDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static CreateJobDto? 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 "CreateJobDto[$key]" is missing from JSON.');
|
||||||
|
assert(json[key] != null, 'Required key "CreateJobDto[$key]" has a null value in JSON.');
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return CreateJobDto(
|
||||||
|
jobType: JobType.fromJson(json[r'jobType'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<CreateJobDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <CreateJobDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = CreateJobDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, CreateJobDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, CreateJobDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = CreateJobDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of CreateJobDto-objects as value to a dart map
|
||||||
|
static Map<String, List<CreateJobDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<CreateJobDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = CreateJobDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'jobType',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -14,12 +14,12 @@ class ExifResponseDto {
|
|||||||
/// Returns a new [ExifResponseDto] instance.
|
/// Returns a new [ExifResponseDto] instance.
|
||||||
ExifResponseDto({
|
ExifResponseDto({
|
||||||
this.id,
|
this.id,
|
||||||
|
this.fileSizeInByte,
|
||||||
this.make,
|
this.make,
|
||||||
this.model,
|
this.model,
|
||||||
this.imageName,
|
this.imageName,
|
||||||
this.exifImageWidth,
|
this.exifImageWidth,
|
||||||
this.exifImageHeight,
|
this.exifImageHeight,
|
||||||
this.fileSizeInByte,
|
|
||||||
this.orientation,
|
this.orientation,
|
||||||
this.dateTimeOriginal,
|
this.dateTimeOriginal,
|
||||||
this.modifyDate,
|
this.modifyDate,
|
||||||
@ -35,7 +35,9 @@ class ExifResponseDto {
|
|||||||
this.country,
|
this.country,
|
||||||
});
|
});
|
||||||
|
|
||||||
String? id;
|
int? id;
|
||||||
|
|
||||||
|
int? fileSizeInByte;
|
||||||
|
|
||||||
String? make;
|
String? make;
|
||||||
|
|
||||||
@ -47,8 +49,6 @@ class ExifResponseDto {
|
|||||||
|
|
||||||
num? exifImageHeight;
|
num? exifImageHeight;
|
||||||
|
|
||||||
num? fileSizeInByte;
|
|
||||||
|
|
||||||
String? orientation;
|
String? orientation;
|
||||||
|
|
||||||
DateTime? dateTimeOriginal;
|
DateTime? dateTimeOriginal;
|
||||||
@ -78,12 +78,12 @@ class ExifResponseDto {
|
|||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is ExifResponseDto &&
|
bool operator ==(Object other) => identical(this, other) || other is ExifResponseDto &&
|
||||||
other.id == id &&
|
other.id == id &&
|
||||||
|
other.fileSizeInByte == fileSizeInByte &&
|
||||||
other.make == make &&
|
other.make == make &&
|
||||||
other.model == model &&
|
other.model == model &&
|
||||||
other.imageName == imageName &&
|
other.imageName == imageName &&
|
||||||
other.exifImageWidth == exifImageWidth &&
|
other.exifImageWidth == exifImageWidth &&
|
||||||
other.exifImageHeight == exifImageHeight &&
|
other.exifImageHeight == exifImageHeight &&
|
||||||
other.fileSizeInByte == fileSizeInByte &&
|
|
||||||
other.orientation == orientation &&
|
other.orientation == orientation &&
|
||||||
other.dateTimeOriginal == dateTimeOriginal &&
|
other.dateTimeOriginal == dateTimeOriginal &&
|
||||||
other.modifyDate == modifyDate &&
|
other.modifyDate == modifyDate &&
|
||||||
@ -102,12 +102,12 @@ class ExifResponseDto {
|
|||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(id == null ? 0 : id!.hashCode) +
|
(id == null ? 0 : id!.hashCode) +
|
||||||
|
(fileSizeInByte == null ? 0 : fileSizeInByte!.hashCode) +
|
||||||
(make == null ? 0 : make!.hashCode) +
|
(make == null ? 0 : make!.hashCode) +
|
||||||
(model == null ? 0 : model!.hashCode) +
|
(model == null ? 0 : model!.hashCode) +
|
||||||
(imageName == null ? 0 : imageName!.hashCode) +
|
(imageName == null ? 0 : imageName!.hashCode) +
|
||||||
(exifImageWidth == null ? 0 : exifImageWidth!.hashCode) +
|
(exifImageWidth == null ? 0 : exifImageWidth!.hashCode) +
|
||||||
(exifImageHeight == null ? 0 : exifImageHeight!.hashCode) +
|
(exifImageHeight == null ? 0 : exifImageHeight!.hashCode) +
|
||||||
(fileSizeInByte == null ? 0 : fileSizeInByte!.hashCode) +
|
|
||||||
(orientation == null ? 0 : orientation!.hashCode) +
|
(orientation == null ? 0 : orientation!.hashCode) +
|
||||||
(dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) +
|
(dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) +
|
||||||
(modifyDate == null ? 0 : modifyDate!.hashCode) +
|
(modifyDate == null ? 0 : modifyDate!.hashCode) +
|
||||||
@ -123,7 +123,7 @@ class ExifResponseDto {
|
|||||||
(country == null ? 0 : country!.hashCode);
|
(country == null ? 0 : country!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'ExifResponseDto[id=$id, make=$make, model=$model, imageName=$imageName, exifImageWidth=$exifImageWidth, exifImageHeight=$exifImageHeight, fileSizeInByte=$fileSizeInByte, orientation=$orientation, dateTimeOriginal=$dateTimeOriginal, modifyDate=$modifyDate, lensModel=$lensModel, fNumber=$fNumber, focalLength=$focalLength, iso=$iso, exposureTime=$exposureTime, latitude=$latitude, longitude=$longitude, city=$city, state=$state, country=$country]';
|
String toString() => 'ExifResponseDto[id=$id, fileSizeInByte=$fileSizeInByte, make=$make, model=$model, imageName=$imageName, exifImageWidth=$exifImageWidth, exifImageHeight=$exifImageHeight, orientation=$orientation, dateTimeOriginal=$dateTimeOriginal, modifyDate=$modifyDate, lensModel=$lensModel, fNumber=$fNumber, focalLength=$focalLength, iso=$iso, exposureTime=$exposureTime, latitude=$latitude, longitude=$longitude, city=$city, state=$state, country=$country]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final _json = <String, dynamic>{};
|
final _json = <String, dynamic>{};
|
||||||
@ -132,6 +132,11 @@ class ExifResponseDto {
|
|||||||
} else {
|
} else {
|
||||||
_json[r'id'] = null;
|
_json[r'id'] = null;
|
||||||
}
|
}
|
||||||
|
if (fileSizeInByte != null) {
|
||||||
|
_json[r'fileSizeInByte'] = fileSizeInByte;
|
||||||
|
} else {
|
||||||
|
_json[r'fileSizeInByte'] = null;
|
||||||
|
}
|
||||||
if (make != null) {
|
if (make != null) {
|
||||||
_json[r'make'] = make;
|
_json[r'make'] = make;
|
||||||
} else {
|
} else {
|
||||||
@ -157,11 +162,6 @@ class ExifResponseDto {
|
|||||||
} else {
|
} else {
|
||||||
_json[r'exifImageHeight'] = null;
|
_json[r'exifImageHeight'] = null;
|
||||||
}
|
}
|
||||||
if (fileSizeInByte != null) {
|
|
||||||
_json[r'fileSizeInByte'] = fileSizeInByte;
|
|
||||||
} else {
|
|
||||||
_json[r'fileSizeInByte'] = null;
|
|
||||||
}
|
|
||||||
if (orientation != null) {
|
if (orientation != null) {
|
||||||
_json[r'orientation'] = orientation;
|
_json[r'orientation'] = orientation;
|
||||||
} else {
|
} else {
|
||||||
@ -249,7 +249,8 @@ class ExifResponseDto {
|
|||||||
}());
|
}());
|
||||||
|
|
||||||
return ExifResponseDto(
|
return ExifResponseDto(
|
||||||
id: mapValueOfType<String>(json, r'id'),
|
id: mapValueOfType<int>(json, r'id'),
|
||||||
|
fileSizeInByte: mapValueOfType<int>(json, r'fileSizeInByte'),
|
||||||
make: mapValueOfType<String>(json, r'make'),
|
make: mapValueOfType<String>(json, r'make'),
|
||||||
model: mapValueOfType<String>(json, r'model'),
|
model: mapValueOfType<String>(json, r'model'),
|
||||||
imageName: mapValueOfType<String>(json, r'imageName'),
|
imageName: mapValueOfType<String>(json, r'imageName'),
|
||||||
@ -259,9 +260,6 @@ class ExifResponseDto {
|
|||||||
exifImageHeight: json[r'exifImageHeight'] == null
|
exifImageHeight: json[r'exifImageHeight'] == null
|
||||||
? null
|
? null
|
||||||
: num.parse(json[r'exifImageHeight'].toString()),
|
: num.parse(json[r'exifImageHeight'].toString()),
|
||||||
fileSizeInByte: json[r'fileSizeInByte'] == null
|
|
||||||
? null
|
|
||||||
: num.parse(json[r'fileSizeInByte'].toString()),
|
|
||||||
orientation: mapValueOfType<String>(json, r'orientation'),
|
orientation: mapValueOfType<String>(json, r'orientation'),
|
||||||
dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', ''),
|
dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', ''),
|
||||||
modifyDate: mapDateTime(json, r'modifyDate', ''),
|
modifyDate: mapDateTime(json, r'modifyDate', ''),
|
||||||
|
85
mobile/openapi/lib/model/job_command.dart
Normal file
85
mobile/openapi/lib/model/job_command.dart
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
//
|
||||||
|
// 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 JobCommand {
|
||||||
|
/// Instantiate a new enum with the provided [value].
|
||||||
|
const JobCommand._(this.value);
|
||||||
|
|
||||||
|
/// The underlying value of this enum member.
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => value;
|
||||||
|
|
||||||
|
String toJson() => value;
|
||||||
|
|
||||||
|
static const start = JobCommand._(r'start');
|
||||||
|
static const stop = JobCommand._(r'stop');
|
||||||
|
|
||||||
|
/// List of all possible values in this [enum][JobCommand].
|
||||||
|
static const values = <JobCommand>[
|
||||||
|
start,
|
||||||
|
stop,
|
||||||
|
];
|
||||||
|
|
||||||
|
static JobCommand? fromJson(dynamic value) => JobCommandTypeTransformer().decode(value);
|
||||||
|
|
||||||
|
static List<JobCommand>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <JobCommand>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = JobCommand.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transformation class that can [encode] an instance of [JobCommand] to String,
|
||||||
|
/// and [decode] dynamic data back to [JobCommand].
|
||||||
|
class JobCommandTypeTransformer {
|
||||||
|
factory JobCommandTypeTransformer() => _instance ??= const JobCommandTypeTransformer._();
|
||||||
|
|
||||||
|
const JobCommandTypeTransformer._();
|
||||||
|
|
||||||
|
String encode(JobCommand data) => data.value;
|
||||||
|
|
||||||
|
/// Decodes a [dynamic value][data] to a JobCommand.
|
||||||
|
///
|
||||||
|
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||||
|
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||||
|
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||||
|
///
|
||||||
|
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||||
|
/// and users are still using an old app with the old code.
|
||||||
|
JobCommand? decode(dynamic data, {bool allowNull = true}) {
|
||||||
|
if (data != null) {
|
||||||
|
switch (data.toString()) {
|
||||||
|
case r'start': return JobCommand.start;
|
||||||
|
case r'stop': return JobCommand.stop;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Singleton [JobCommandTypeTransformer] instance.
|
||||||
|
static JobCommandTypeTransformer? _instance;
|
||||||
|
}
|
||||||
|
|
111
mobile/openapi/lib/model/job_command_dto.dart
Normal file
111
mobile/openapi/lib/model/job_command_dto.dart
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
//
|
||||||
|
// 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 JobCommandDto {
|
||||||
|
/// Returns a new [JobCommandDto] instance.
|
||||||
|
JobCommandDto({
|
||||||
|
required this.command,
|
||||||
|
});
|
||||||
|
|
||||||
|
JobCommand command;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is JobCommandDto &&
|
||||||
|
other.command == command;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(command.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'JobCommandDto[command=$command]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final _json = <String, dynamic>{};
|
||||||
|
_json[r'command'] = command;
|
||||||
|
return _json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [JobCommandDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static JobCommandDto? 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 "JobCommandDto[$key]" is missing from JSON.');
|
||||||
|
assert(json[key] != null, 'Required key "JobCommandDto[$key]" has a null value in JSON.');
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return JobCommandDto(
|
||||||
|
command: JobCommand.fromJson(json[r'command'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<JobCommandDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <JobCommandDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = JobCommandDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, JobCommandDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, JobCommandDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = JobCommandDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of JobCommandDto-objects as value to a dart map
|
||||||
|
static Map<String, List<JobCommandDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<JobCommandDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = JobCommandDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'command',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
153
mobile/openapi/lib/model/job_counts.dart
Normal file
153
mobile/openapi/lib/model/job_counts.dart
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
//
|
||||||
|
// 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 JobCounts {
|
||||||
|
/// Returns a new [JobCounts] instance.
|
||||||
|
JobCounts({
|
||||||
|
required this.active,
|
||||||
|
required this.completed,
|
||||||
|
required this.failed,
|
||||||
|
required this.delayed,
|
||||||
|
required this.waiting,
|
||||||
|
});
|
||||||
|
|
||||||
|
num active;
|
||||||
|
|
||||||
|
num completed;
|
||||||
|
|
||||||
|
num failed;
|
||||||
|
|
||||||
|
num delayed;
|
||||||
|
|
||||||
|
num waiting;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is JobCounts &&
|
||||||
|
other.active == active &&
|
||||||
|
other.completed == completed &&
|
||||||
|
other.failed == failed &&
|
||||||
|
other.delayed == delayed &&
|
||||||
|
other.waiting == waiting;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(active.hashCode) +
|
||||||
|
(completed.hashCode) +
|
||||||
|
(failed.hashCode) +
|
||||||
|
(delayed.hashCode) +
|
||||||
|
(waiting.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'JobCounts[active=$active, completed=$completed, failed=$failed, delayed=$delayed, waiting=$waiting]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final _json = <String, dynamic>{};
|
||||||
|
_json[r'active'] = active;
|
||||||
|
_json[r'completed'] = completed;
|
||||||
|
_json[r'failed'] = failed;
|
||||||
|
_json[r'delayed'] = delayed;
|
||||||
|
_json[r'waiting'] = waiting;
|
||||||
|
return _json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [JobCounts] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static JobCounts? 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 "JobCounts[$key]" is missing from JSON.');
|
||||||
|
assert(json[key] != null, 'Required key "JobCounts[$key]" has a null value in JSON.');
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return JobCounts(
|
||||||
|
active: json[r'active'] == null
|
||||||
|
? null
|
||||||
|
: num.parse(json[r'active'].toString()),
|
||||||
|
completed: json[r'completed'] == null
|
||||||
|
? null
|
||||||
|
: num.parse(json[r'completed'].toString()),
|
||||||
|
failed: json[r'failed'] == null
|
||||||
|
? null
|
||||||
|
: num.parse(json[r'failed'].toString()),
|
||||||
|
delayed: json[r'delayed'] == null
|
||||||
|
? null
|
||||||
|
: num.parse(json[r'delayed'].toString()),
|
||||||
|
waiting: json[r'waiting'] == null
|
||||||
|
? null
|
||||||
|
: num.parse(json[r'waiting'].toString()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<JobCounts>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <JobCounts>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = JobCounts.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, JobCounts> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, JobCounts>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = JobCounts.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of JobCounts-objects as value to a dart map
|
||||||
|
static Map<String, List<JobCounts>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<JobCounts>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = JobCounts.listFromJson(entry.value, growable: growable,);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'active',
|
||||||
|
'completed',
|
||||||
|
'failed',
|
||||||
|
'delayed',
|
||||||
|
'waiting',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
91
mobile/openapi/lib/model/job_id.dart
Normal file
91
mobile/openapi/lib/model/job_id.dart
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
//
|
||||||
|
// 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 JobId {
|
||||||
|
/// Instantiate a new enum with the provided [value].
|
||||||
|
const JobId._(this.value);
|
||||||
|
|
||||||
|
/// The underlying value of this enum member.
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => value;
|
||||||
|
|
||||||
|
String toJson() => value;
|
||||||
|
|
||||||
|
static const thumbnailGeneration = JobId._(r'thumbnail-generation');
|
||||||
|
static const metadataExtraction = JobId._(r'metadata-extraction');
|
||||||
|
static const videoConversion = JobId._(r'video-conversion');
|
||||||
|
static const machineLearning = JobId._(r'machine-learning');
|
||||||
|
|
||||||
|
/// List of all possible values in this [enum][JobId].
|
||||||
|
static const values = <JobId>[
|
||||||
|
thumbnailGeneration,
|
||||||
|
metadataExtraction,
|
||||||
|
videoConversion,
|
||||||
|
machineLearning,
|
||||||
|
];
|
||||||
|
|
||||||
|
static JobId? fromJson(dynamic value) => JobIdTypeTransformer().decode(value);
|
||||||
|
|
||||||
|
static List<JobId>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <JobId>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = JobId.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transformation class that can [encode] an instance of [JobId] to String,
|
||||||
|
/// and [decode] dynamic data back to [JobId].
|
||||||
|
class JobIdTypeTransformer {
|
||||||
|
factory JobIdTypeTransformer() => _instance ??= const JobIdTypeTransformer._();
|
||||||
|
|
||||||
|
const JobIdTypeTransformer._();
|
||||||
|
|
||||||
|
String encode(JobId data) => data.value;
|
||||||
|
|
||||||
|
/// Decodes a [dynamic value][data] to a JobId.
|
||||||
|
///
|
||||||
|
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||||
|
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||||
|
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||||
|
///
|
||||||
|
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||||
|
/// and users are still using an old app with the old code.
|
||||||
|
JobId? decode(dynamic data, {bool allowNull = true}) {
|
||||||
|
if (data != null) {
|
||||||
|
switch (data.toString()) {
|
||||||
|
case r'thumbnail-generation': return JobId.thumbnailGeneration;
|
||||||
|
case r'metadata-extraction': return JobId.metadataExtraction;
|
||||||
|
case r'video-conversion': return JobId.videoConversion;
|
||||||
|
case r'machine-learning': return JobId.machineLearning;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Singleton [JobIdTypeTransformer] instance.
|
||||||
|
static JobIdTypeTransformer? _instance;
|
||||||
|
}
|
||||||
|
|
119
mobile/openapi/lib/model/job_status_response_dto.dart
Normal file
119
mobile/openapi/lib/model/job_status_response_dto.dart
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
//
|
||||||
|
// 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 JobStatusResponseDto {
|
||||||
|
/// Returns a new [JobStatusResponseDto] instance.
|
||||||
|
JobStatusResponseDto({
|
||||||
|
required this.isActive,
|
||||||
|
required this.queueCount,
|
||||||
|
});
|
||||||
|
|
||||||
|
bool isActive;
|
||||||
|
|
||||||
|
Object queueCount;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is JobStatusResponseDto &&
|
||||||
|
other.isActive == isActive &&
|
||||||
|
other.queueCount == queueCount;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(isActive.hashCode) +
|
||||||
|
(queueCount.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'JobStatusResponseDto[isActive=$isActive, queueCount=$queueCount]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final _json = <String, dynamic>{};
|
||||||
|
_json[r'isActive'] = isActive;
|
||||||
|
_json[r'queueCount'] = queueCount;
|
||||||
|
return _json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [JobStatusResponseDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static JobStatusResponseDto? 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 "JobStatusResponseDto[$key]" is missing from JSON.');
|
||||||
|
assert(json[key] != null, 'Required key "JobStatusResponseDto[$key]" has a null value in JSON.');
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return JobStatusResponseDto(
|
||||||
|
isActive: mapValueOfType<bool>(json, r'isActive')!,
|
||||||
|
queueCount: mapValueOfType<Object>(json, r'queueCount')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<JobStatusResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <JobStatusResponseDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = JobStatusResponseDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, JobStatusResponseDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, JobStatusResponseDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = JobStatusResponseDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of JobStatusResponseDto-objects as value to a dart map
|
||||||
|
static Map<String, List<JobStatusResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<JobStatusResponseDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = JobStatusResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'isActive',
|
||||||
|
'queueCount',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
91
mobile/openapi/lib/model/job_type.dart
Normal file
91
mobile/openapi/lib/model/job_type.dart
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
//
|
||||||
|
// 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 JobType {
|
||||||
|
/// Instantiate a new enum with the provided [value].
|
||||||
|
const JobType._(this.value);
|
||||||
|
|
||||||
|
/// The underlying value of this enum member.
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => value;
|
||||||
|
|
||||||
|
String toJson() => value;
|
||||||
|
|
||||||
|
static const THUMBNAIL_GENERATION = JobType._(r'THUMBNAIL_GENERATION');
|
||||||
|
static const METADATA_EXTRACTION = JobType._(r'METADATA_EXTRACTION');
|
||||||
|
static const VIDEO_CONVERSION = JobType._(r'VIDEO_CONVERSION');
|
||||||
|
static const CHECKSUM_GENERATION = JobType._(r'CHECKSUM_GENERATION');
|
||||||
|
|
||||||
|
/// List of all possible values in this [enum][JobType].
|
||||||
|
static const values = <JobType>[
|
||||||
|
THUMBNAIL_GENERATION,
|
||||||
|
METADATA_EXTRACTION,
|
||||||
|
VIDEO_CONVERSION,
|
||||||
|
CHECKSUM_GENERATION,
|
||||||
|
];
|
||||||
|
|
||||||
|
static JobType? fromJson(dynamic value) => JobTypeTypeTransformer().decode(value);
|
||||||
|
|
||||||
|
static List<JobType>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <JobType>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = JobType.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transformation class that can [encode] an instance of [JobType] to String,
|
||||||
|
/// and [decode] dynamic data back to [JobType].
|
||||||
|
class JobTypeTypeTransformer {
|
||||||
|
factory JobTypeTypeTransformer() => _instance ??= const JobTypeTypeTransformer._();
|
||||||
|
|
||||||
|
const JobTypeTypeTransformer._();
|
||||||
|
|
||||||
|
String encode(JobType data) => data.value;
|
||||||
|
|
||||||
|
/// Decodes a [dynamic value][data] to a JobType.
|
||||||
|
///
|
||||||
|
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||||
|
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||||
|
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||||
|
///
|
||||||
|
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||||
|
/// and users are still using an old app with the old code.
|
||||||
|
JobType? decode(dynamic data, {bool allowNull = true}) {
|
||||||
|
if (data != null) {
|
||||||
|
switch (data.toString()) {
|
||||||
|
case r'THUMBNAIL_GENERATION': return JobType.THUMBNAIL_GENERATION;
|
||||||
|
case r'METADATA_EXTRACTION': return JobType.METADATA_EXTRACTION;
|
||||||
|
case r'VIDEO_CONVERSION': return JobType.VIDEO_CONVERSION;
|
||||||
|
case r'CHECKSUM_GENERATION': return JobType.CHECKSUM_GENERATION;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Singleton [JobTypeTypeTransformer] instance.
|
||||||
|
static JobTypeTypeTransformer? _instance;
|
||||||
|
}
|
||||||
|
|
52
mobile/openapi/test/all_job_status_response_dto_test.dart
Normal file
52
mobile/openapi/test/all_job_status_response_dto_test.dart
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
//
|
||||||
|
// 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 AllJobStatusResponseDto
|
||||||
|
void main() {
|
||||||
|
// final instance = AllJobStatusResponseDto();
|
||||||
|
|
||||||
|
group('test AllJobStatusResponseDto', () {
|
||||||
|
// bool isThumbnailGenerationActive
|
||||||
|
test('to test the property `isThumbnailGenerationActive`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// Object thumbnailGenerationQueueCount
|
||||||
|
test('to test the property `thumbnailGenerationQueueCount`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// bool isMetadataExtractionActive
|
||||||
|
test('to test the property `isMetadataExtractionActive`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// Object metadataExtractionQueueCount
|
||||||
|
test('to test the property `metadataExtractionQueueCount`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// bool isVideoConversionActive
|
||||||
|
test('to test the property `isVideoConversionActive`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// Object videoConversionQueueCount
|
||||||
|
test('to test the property `videoConversionQueueCount`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
27
mobile/openapi/test/create_job_dto_test.dart
Normal file
27
mobile/openapi/test/create_job_dto_test.dart
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// 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 CreateJobDto
|
||||||
|
void main() {
|
||||||
|
// final instance = CreateJobDto();
|
||||||
|
|
||||||
|
group('test CreateJobDto', () {
|
||||||
|
// JobType jobType
|
||||||
|
test('to test the property `jobType`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
41
mobile/openapi/test/job_api_test.dart
Normal file
41
mobile/openapi/test/job_api_test.dart
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// 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 JobApi
|
||||||
|
void main() {
|
||||||
|
// final instance = JobApi();
|
||||||
|
|
||||||
|
group('tests for JobApi', () {
|
||||||
|
//Future<Object> create(CreateJobDto createJobDto) async
|
||||||
|
test('test create', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
//Future<AllJobStatusResponseDto> getAllJobsStatus() async
|
||||||
|
test('test getAllJobsStatus', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
//Future<JobStatusResponseDto> getJobStatus(JobType jobType) async
|
||||||
|
test('test getJobStatus', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
//Future<JobStatusResponseDto> stopJob(JobType jobType) async
|
||||||
|
test('test stopJob', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
27
mobile/openapi/test/job_command_dto_test.dart
Normal file
27
mobile/openapi/test/job_command_dto_test.dart
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// 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 JobCommandDto
|
||||||
|
void main() {
|
||||||
|
// final instance = JobCommandDto();
|
||||||
|
|
||||||
|
group('test JobCommandDto', () {
|
||||||
|
// JobCommand command
|
||||||
|
test('to test the property `command`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
21
mobile/openapi/test/job_command_test.dart
Normal file
21
mobile/openapi/test/job_command_test.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// 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 JobCommand
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
group('test JobCommand', () {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
47
mobile/openapi/test/job_counts_test.dart
Normal file
47
mobile/openapi/test/job_counts_test.dart
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// 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 JobCounts
|
||||||
|
void main() {
|
||||||
|
// final instance = JobCounts();
|
||||||
|
|
||||||
|
group('test JobCounts', () {
|
||||||
|
// num active
|
||||||
|
test('to test the property `active`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// num completed
|
||||||
|
test('to test the property `completed`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// num failed
|
||||||
|
test('to test the property `failed`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// num delayed
|
||||||
|
test('to test the property `delayed`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// num waiting
|
||||||
|
test('to test the property `waiting`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
21
mobile/openapi/test/job_id_test.dart
Normal file
21
mobile/openapi/test/job_id_test.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// 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 JobId
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
group('test JobId', () {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
32
mobile/openapi/test/job_status_response_dto_test.dart
Normal file
32
mobile/openapi/test/job_status_response_dto_test.dart
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// 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 JobStatusResponseDto
|
||||||
|
void main() {
|
||||||
|
// final instance = JobStatusResponseDto();
|
||||||
|
|
||||||
|
group('test JobStatusResponseDto', () {
|
||||||
|
// bool isActive
|
||||||
|
test('to test the property `isActive`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// Object queueCount
|
||||||
|
test('to test the property `queueCount`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
21
mobile/openapi/test/job_type_test.dart
Normal file
21
mobile/openapi/test/job_type_test.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// 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 JobType
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
group('test JobType', () {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
upload/
|
upload/
|
||||||
dist/
|
dist/
|
||||||
|
.reverse-geocoding-dump
|
||||||
|
@ -134,6 +134,9 @@ describe('Album service', () => {
|
|||||||
getAssetByTimeBucket: jest.fn(),
|
getAssetByTimeBucket: jest.fn(),
|
||||||
getAssetByChecksum: jest.fn(),
|
getAssetByChecksum: jest.fn(),
|
||||||
getAssetCountByUserId: jest.fn(),
|
getAssetCountByUserId: jest.fn(),
|
||||||
|
getAssetWithNoEXIF: jest.fn(),
|
||||||
|
getAssetWithNoThumbnail: jest.fn(),
|
||||||
|
getAssetWithNoSmartInfo: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
sut = new AlbumService(albumRepositoryMock, assetRepositoryMock);
|
sut = new AlbumService(albumRepositoryMock, assetRepositoryMock);
|
||||||
|
@ -29,6 +29,9 @@ export interface IAssetRepository {
|
|||||||
getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto>;
|
getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto>;
|
||||||
getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]>;
|
getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]>;
|
||||||
getAssetByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity>;
|
getAssetByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity>;
|
||||||
|
getAssetWithNoThumbnail(): Promise<AssetEntity[]>;
|
||||||
|
getAssetWithNoEXIF(): Promise<AssetEntity[]>;
|
||||||
|
getAssetWithNoSmartInfo(): Promise<AssetEntity[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ASSET_REPOSITORY = 'ASSET_REPOSITORY';
|
export const ASSET_REPOSITORY = 'ASSET_REPOSITORY';
|
||||||
@ -40,6 +43,33 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
private assetRepository: Repository<AssetEntity>,
|
private assetRepository: Repository<AssetEntity>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async getAssetWithNoSmartInfo(): Promise<AssetEntity[]> {
|
||||||
|
return await this.assetRepository
|
||||||
|
.createQueryBuilder('asset')
|
||||||
|
.leftJoinAndSelect('asset.smartInfo', 'si')
|
||||||
|
.where('asset.resizePath IS NOT NULL')
|
||||||
|
.andWhere('si.id IS NULL')
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAssetWithNoThumbnail(): Promise<AssetEntity[]> {
|
||||||
|
return await this.assetRepository
|
||||||
|
.createQueryBuilder('asset')
|
||||||
|
.where('asset.resizePath IS NULL')
|
||||||
|
.orWhere('asset.resizePath = :resizePath', { resizePath: '' })
|
||||||
|
.orWhere('asset.webpPath IS NULL')
|
||||||
|
.orWhere('asset.webpPath = :webpPath', { webpPath: '' })
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAssetWithNoEXIF(): Promise<AssetEntity[]> {
|
||||||
|
return await this.assetRepository
|
||||||
|
.createQueryBuilder('asset')
|
||||||
|
.leftJoinAndSelect('asset.exifInfo', 'ei')
|
||||||
|
.where('ei."assetId" IS NULL')
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
async getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto> {
|
async getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto> {
|
||||||
// Get asset count by AssetType
|
// Get asset count by AssetType
|
||||||
const res = await this.assetRepository
|
const res = await this.assetRepository
|
||||||
|
@ -30,7 +30,7 @@ import { CommunicationGateway } from '../communication/communication.gateway';
|
|||||||
import { InjectQueue } from '@nestjs/bull';
|
import { InjectQueue } from '@nestjs/bull';
|
||||||
import { Queue } from 'bull';
|
import { Queue } from 'bull';
|
||||||
import { IAssetUploadedJob } from '@app/job/index';
|
import { IAssetUploadedJob } from '@app/job/index';
|
||||||
import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant';
|
import { QueueNameEnum } from '@app/job/constants/queue-name.constant';
|
||||||
import { assetUploadedProcessorName } from '@app/job/constants/job-name.constant';
|
import { assetUploadedProcessorName } from '@app/job/constants/job-name.constant';
|
||||||
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
||||||
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||||
@ -59,7 +59,7 @@ export class AssetController {
|
|||||||
private assetService: AssetService,
|
private assetService: AssetService,
|
||||||
private backgroundTaskService: BackgroundTaskService,
|
private backgroundTaskService: BackgroundTaskService,
|
||||||
|
|
||||||
@InjectQueue(assetUploadedQueueName)
|
@InjectQueue(QueueNameEnum.ASSET_UPLOADED)
|
||||||
private assetUploadedQueue: Queue<IAssetUploadedJob>,
|
private assetUploadedQueue: Queue<IAssetUploadedJob>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import { BullModule } from '@nestjs/bull';
|
|||||||
import { BackgroundTaskModule } from '../../modules/background-task/background-task.module';
|
import { BackgroundTaskModule } from '../../modules/background-task/background-task.module';
|
||||||
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
|
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
|
||||||
import { CommunicationModule } from '../communication/communication.module';
|
import { CommunicationModule } from '../communication/communication.module';
|
||||||
import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant';
|
import { QueueNameEnum } from '@app/job/constants/queue-name.constant';
|
||||||
import { AssetRepository, ASSET_REPOSITORY } from './asset-repository';
|
import { AssetRepository, ASSET_REPOSITORY } from './asset-repository';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@ -16,7 +16,7 @@ import { AssetRepository, ASSET_REPOSITORY } from './asset-repository';
|
|||||||
BackgroundTaskModule,
|
BackgroundTaskModule,
|
||||||
TypeOrmModule.forFeature([AssetEntity]),
|
TypeOrmModule.forFeature([AssetEntity]),
|
||||||
BullModule.registerQueue({
|
BullModule.registerQueue({
|
||||||
name: assetUploadedQueueName,
|
name: QueueNameEnum.ASSET_UPLOADED,
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
|
@ -107,6 +107,9 @@ describe('AssetService', () => {
|
|||||||
getAssetByTimeBucket: jest.fn(),
|
getAssetByTimeBucket: jest.fn(),
|
||||||
getAssetByChecksum: jest.fn(),
|
getAssetByChecksum: jest.fn(),
|
||||||
getAssetCountByUserId: jest.fn(),
|
getAssetCountByUserId: jest.fn(),
|
||||||
|
getAssetWithNoEXIF: jest.fn(),
|
||||||
|
getAssetWithNoThumbnail: jest.fn(),
|
||||||
|
getAssetWithNoSmartInfo: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
sui = new AssetService(assetRepositoryMock, a);
|
sui = new AssetService(assetRepositoryMock, a);
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import { ExifEntity } from '@app/database/entities/exif.entity';
|
import { ExifEntity } from '@app/database/entities/exif.entity';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class ExifResponseDto {
|
export class ExifResponseDto {
|
||||||
id?: string | null = null;
|
@ApiProperty({ type: 'integer', format: 'int64' })
|
||||||
|
id?: number | null = null;
|
||||||
make?: string | null = null;
|
make?: string | null = null;
|
||||||
model?: string | null = null;
|
model?: string | null = null;
|
||||||
imageName?: string | null = null;
|
imageName?: string | null = null;
|
||||||
exifImageWidth?: number | null = null;
|
exifImageWidth?: number | null = null;
|
||||||
exifImageHeight?: number | null = null;
|
exifImageHeight?: number | null = null;
|
||||||
|
|
||||||
|
@ApiProperty({ type: 'integer', format: 'int64' })
|
||||||
fileSizeInByte?: number | null = null;
|
fileSizeInByte?: number | null = null;
|
||||||
orientation?: string | null = null;
|
orientation?: string | null = null;
|
||||||
dateTimeOriginal?: Date | null = null;
|
dateTimeOriginal?: Date | null = null;
|
||||||
@ -25,13 +29,13 @@ export class ExifResponseDto {
|
|||||||
|
|
||||||
export function mapExif(entity: ExifEntity): ExifResponseDto {
|
export function mapExif(entity: ExifEntity): ExifResponseDto {
|
||||||
return {
|
return {
|
||||||
id: entity.id,
|
id: parseInt(entity.id),
|
||||||
make: entity.make,
|
make: entity.make,
|
||||||
model: entity.model,
|
model: entity.model,
|
||||||
imageName: entity.imageName,
|
imageName: entity.imageName,
|
||||||
exifImageWidth: entity.exifImageWidth,
|
exifImageWidth: entity.exifImageWidth,
|
||||||
exifImageHeight: entity.exifImageHeight,
|
exifImageHeight: entity.exifImageHeight,
|
||||||
fileSizeInByte: entity.fileSizeInByte,
|
fileSizeInByte: entity.fileSizeInByte ? parseInt(entity.fileSizeInByte.toString()) : null,
|
||||||
orientation: entity.orientation,
|
orientation: entity.orientation,
|
||||||
dateTimeOriginal: entity.dateTimeOriginal,
|
dateTimeOriginal: entity.dateTimeOriginal,
|
||||||
modifyDate: entity.modifyDate,
|
modifyDate: entity.modifyDate,
|
||||||
|
21
server/apps/immich/src/api-v1/job/dto/get-job.dto.ts
Normal file
21
server/apps/immich/src/api-v1/job/dto/get-job.dto.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsEnum, IsNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
|
export enum JobId {
|
||||||
|
THUMBNAIL_GENERATION = 'thumbnail-generation',
|
||||||
|
METADATA_EXTRACTION = 'metadata-extraction',
|
||||||
|
VIDEO_CONVERSION = 'video-conversion',
|
||||||
|
MACHINE_LEARNING = 'machine-learning',
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetJobDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsEnum(JobId, {
|
||||||
|
message: `params must be one of ${Object.values(JobId).join()}`,
|
||||||
|
})
|
||||||
|
@ApiProperty({
|
||||||
|
enum: JobId,
|
||||||
|
enumName: 'JobId',
|
||||||
|
})
|
||||||
|
jobId!: string;
|
||||||
|
}
|
12
server/apps/immich/src/api-v1/job/dto/job-command.dto.ts
Normal file
12
server/apps/immich/src/api-v1/job/dto/job-command.dto.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsIn, IsNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
|
export class JobCommandDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsIn(['start', 'stop'])
|
||||||
|
@ApiProperty({
|
||||||
|
enum: ['start', 'stop'],
|
||||||
|
enumName: 'JobCommand',
|
||||||
|
})
|
||||||
|
command!: string;
|
||||||
|
}
|
43
server/apps/immich/src/api-v1/job/job.controller.ts
Normal file
43
server/apps/immich/src/api-v1/job/job.controller.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { Controller, Get, Body, UseGuards, ValidationPipe, Put, Param } from '@nestjs/common';
|
||||||
|
import { JobService } from './job.service';
|
||||||
|
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
||||||
|
import { AdminRolesGuard } from '../../middlewares/admin-role-guard.middleware';
|
||||||
|
import { AllJobStatusResponseDto } from './response-dto/all-job-status-response.dto';
|
||||||
|
import { GetJobDto } from './dto/get-job.dto';
|
||||||
|
import { JobStatusResponseDto } from './response-dto/job-status-response.dto';
|
||||||
|
|
||||||
|
import { JobCommandDto } from './dto/job-command.dto';
|
||||||
|
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@UseGuards(AdminRolesGuard)
|
||||||
|
@ApiTags('Job')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Controller('jobs')
|
||||||
|
export class JobController {
|
||||||
|
constructor(private readonly jobService: JobService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
getAllJobsStatus(): Promise<AllJobStatusResponseDto> {
|
||||||
|
return this.jobService.getAllJobsStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/:jobId')
|
||||||
|
getJobStatus(@Param(ValidationPipe) params: GetJobDto): Promise<JobStatusResponseDto> {
|
||||||
|
return this.jobService.getJobStatus(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('/:jobId')
|
||||||
|
async sendJobCommand(
|
||||||
|
@Param(ValidationPipe) params: GetJobDto,
|
||||||
|
@Body(ValidationPipe) body: JobCommandDto,
|
||||||
|
): Promise<number> {
|
||||||
|
if (body.command === 'start') {
|
||||||
|
return await this.jobService.startJob(params);
|
||||||
|
}
|
||||||
|
if (body.command === 'stop') {
|
||||||
|
return await this.jobService.stopJob(params);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
82
server/apps/immich/src/api-v1/job/job.module.ts
Normal file
82
server/apps/immich/src/api-v1/job/job.module.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { JobService } from './job.service';
|
||||||
|
import { JobController } from './job.controller';
|
||||||
|
import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service';
|
||||||
|
import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module';
|
||||||
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
|
import { jwtConfig } from '../../config/jwt.config';
|
||||||
|
import { UserEntity } from '@app/database/entities/user.entity';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { BullModule } from '@nestjs/bull';
|
||||||
|
import { QueueNameEnum } from '@app/job';
|
||||||
|
import { AssetEntity } from '@app/database/entities/asset.entity';
|
||||||
|
import { ExifEntity } from '@app/database/entities/exif.entity';
|
||||||
|
import { AssetRepository, ASSET_REPOSITORY } from '../asset/asset-repository';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([UserEntity, AssetEntity, ExifEntity]),
|
||||||
|
ImmichJwtModule,
|
||||||
|
JwtModule.register(jwtConfig),
|
||||||
|
BullModule.registerQueue(
|
||||||
|
{
|
||||||
|
name: QueueNameEnum.THUMBNAIL_GENERATION,
|
||||||
|
defaultJobOptions: {
|
||||||
|
attempts: 3,
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: QueueNameEnum.ASSET_UPLOADED,
|
||||||
|
defaultJobOptions: {
|
||||||
|
attempts: 3,
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: QueueNameEnum.METADATA_EXTRACTION,
|
||||||
|
defaultJobOptions: {
|
||||||
|
attempts: 3,
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: QueueNameEnum.VIDEO_CONVERSION,
|
||||||
|
defaultJobOptions: {
|
||||||
|
attempts: 3,
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: QueueNameEnum.CHECKSUM_GENERATION,
|
||||||
|
defaultJobOptions: {
|
||||||
|
attempts: 3,
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: QueueNameEnum.MACHINE_LEARNING,
|
||||||
|
defaultJobOptions: {
|
||||||
|
attempts: 3,
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
controllers: [JobController],
|
||||||
|
providers: [
|
||||||
|
JobService,
|
||||||
|
ImmichJwtService,
|
||||||
|
{
|
||||||
|
provide: ASSET_REPOSITORY,
|
||||||
|
useClass: AssetRepository,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class JobModule {}
|
180
server/apps/immich/src/api-v1/job/job.service.ts
Normal file
180
server/apps/immich/src/api-v1/job/job.service.ts
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
import {
|
||||||
|
exifExtractionProcessorName,
|
||||||
|
generateJPEGThumbnailProcessorName,
|
||||||
|
IMetadataExtractionJob,
|
||||||
|
IThumbnailGenerationJob,
|
||||||
|
IVideoTranscodeJob,
|
||||||
|
MachineLearningJobNameEnum,
|
||||||
|
QueueNameEnum,
|
||||||
|
videoMetadataExtractionProcessorName,
|
||||||
|
} from '@app/job';
|
||||||
|
import { InjectQueue } from '@nestjs/bull';
|
||||||
|
import { Queue } from 'bull';
|
||||||
|
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { AllJobStatusResponseDto } from './response-dto/all-job-status-response.dto';
|
||||||
|
import { randomUUID } from 'crypto';
|
||||||
|
import { ASSET_REPOSITORY, IAssetRepository } from '../asset/asset-repository';
|
||||||
|
import { AssetType } from '@app/database/entities/asset.entity';
|
||||||
|
import { GetJobDto, JobId } from './dto/get-job.dto';
|
||||||
|
import { JobStatusResponseDto } from './response-dto/job-status-response.dto';
|
||||||
|
import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JobService {
|
||||||
|
constructor(
|
||||||
|
@InjectQueue(QueueNameEnum.THUMBNAIL_GENERATION)
|
||||||
|
private thumbnailGeneratorQueue: Queue<IThumbnailGenerationJob>,
|
||||||
|
|
||||||
|
@InjectQueue(QueueNameEnum.METADATA_EXTRACTION)
|
||||||
|
private metadataExtractionQueue: Queue<IMetadataExtractionJob>,
|
||||||
|
|
||||||
|
@InjectQueue(QueueNameEnum.VIDEO_CONVERSION)
|
||||||
|
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
||||||
|
|
||||||
|
@InjectQueue(QueueNameEnum.MACHINE_LEARNING)
|
||||||
|
private machineLearningQueue: Queue<IMachineLearningJob>,
|
||||||
|
|
||||||
|
@Inject(ASSET_REPOSITORY)
|
||||||
|
private _assetRepository: IAssetRepository,
|
||||||
|
) {
|
||||||
|
this.thumbnailGeneratorQueue.empty();
|
||||||
|
this.metadataExtractionQueue.empty();
|
||||||
|
this.videoConversionQueue.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
async startJob(jobDto: GetJobDto): Promise<number> {
|
||||||
|
switch (jobDto.jobId) {
|
||||||
|
case JobId.THUMBNAIL_GENERATION:
|
||||||
|
return this.runThumbnailGenerationJob();
|
||||||
|
case JobId.METADATA_EXTRACTION:
|
||||||
|
return this.runMetadataExtractionJob();
|
||||||
|
case JobId.VIDEO_CONVERSION:
|
||||||
|
return 0;
|
||||||
|
case JobId.MACHINE_LEARNING:
|
||||||
|
return this.runMachineLearningPipeline();
|
||||||
|
default:
|
||||||
|
throw new BadRequestException('Invalid job id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllJobsStatus(): Promise<AllJobStatusResponseDto> {
|
||||||
|
const thumbnailGeneratorJobCount = await this.thumbnailGeneratorQueue.getJobCounts();
|
||||||
|
const metadataExtractionJobCount = await this.metadataExtractionQueue.getJobCounts();
|
||||||
|
const videoConversionJobCount = await this.videoConversionQueue.getJobCounts();
|
||||||
|
const machineLearningJobCount = await this.machineLearningQueue.getJobCounts();
|
||||||
|
|
||||||
|
const response = new AllJobStatusResponseDto();
|
||||||
|
response.isThumbnailGenerationActive = Boolean(thumbnailGeneratorJobCount.waiting);
|
||||||
|
response.thumbnailGenerationQueueCount = thumbnailGeneratorJobCount;
|
||||||
|
response.isMetadataExtractionActive = Boolean(metadataExtractionJobCount.waiting);
|
||||||
|
response.metadataExtractionQueueCount = metadataExtractionJobCount;
|
||||||
|
response.isVideoConversionActive = Boolean(videoConversionJobCount.waiting);
|
||||||
|
response.videoConversionQueueCount = videoConversionJobCount;
|
||||||
|
response.isMachineLearningActive = Boolean(machineLearningJobCount.waiting);
|
||||||
|
response.machineLearningQueueCount = machineLearningJobCount;
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getJobStatus(query: GetJobDto): Promise<JobStatusResponseDto> {
|
||||||
|
const response = new JobStatusResponseDto();
|
||||||
|
if (query.jobId === JobId.THUMBNAIL_GENERATION) {
|
||||||
|
response.isActive = Boolean((await this.thumbnailGeneratorQueue.getJobCounts()).waiting);
|
||||||
|
response.queueCount = await this.thumbnailGeneratorQueue.getJobCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.jobId === JobId.METADATA_EXTRACTION) {
|
||||||
|
response.isActive = Boolean((await this.metadataExtractionQueue.getJobCounts()).waiting);
|
||||||
|
response.queueCount = await this.metadataExtractionQueue.getJobCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.jobId === JobId.VIDEO_CONVERSION) {
|
||||||
|
response.isActive = Boolean((await this.videoConversionQueue.getJobCounts()).waiting);
|
||||||
|
response.queueCount = await this.videoConversionQueue.getJobCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopJob(query: GetJobDto): Promise<number> {
|
||||||
|
switch (query.jobId) {
|
||||||
|
case JobId.THUMBNAIL_GENERATION:
|
||||||
|
this.thumbnailGeneratorQueue.empty();
|
||||||
|
return 0;
|
||||||
|
case JobId.METADATA_EXTRACTION:
|
||||||
|
this.metadataExtractionQueue.empty();
|
||||||
|
return 0;
|
||||||
|
case JobId.VIDEO_CONVERSION:
|
||||||
|
this.videoConversionQueue.empty();
|
||||||
|
return 0;
|
||||||
|
case JobId.MACHINE_LEARNING:
|
||||||
|
this.machineLearningQueue.empty();
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
throw new BadRequestException('Invalid job id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async runThumbnailGenerationJob(): Promise<number> {
|
||||||
|
const jobCount = await this.thumbnailGeneratorQueue.getJobCounts();
|
||||||
|
|
||||||
|
if (jobCount.waiting > 0) {
|
||||||
|
throw new BadRequestException('Thumbnail generation job is already running');
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetsWithNoThumbnail = await this._assetRepository.getAssetWithNoThumbnail();
|
||||||
|
|
||||||
|
for (const asset of assetsWithNoThumbnail) {
|
||||||
|
await this.thumbnailGeneratorQueue.add(generateJPEGThumbnailProcessorName, { asset }, { jobId: randomUUID() });
|
||||||
|
}
|
||||||
|
|
||||||
|
return assetsWithNoThumbnail.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async runMetadataExtractionJob(): Promise<number> {
|
||||||
|
const jobCount = await this.metadataExtractionQueue.getJobCounts();
|
||||||
|
|
||||||
|
if (jobCount.waiting > 0) {
|
||||||
|
throw new BadRequestException('Metadata extraction job is already running');
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetsWithNoExif = await this._assetRepository.getAssetWithNoEXIF();
|
||||||
|
for (const asset of assetsWithNoExif) {
|
||||||
|
if (asset.type === AssetType.VIDEO) {
|
||||||
|
await this.metadataExtractionQueue.add(
|
||||||
|
videoMetadataExtractionProcessorName,
|
||||||
|
{ asset, fileName: asset.id },
|
||||||
|
{ jobId: randomUUID() },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await this.metadataExtractionQueue.add(
|
||||||
|
exifExtractionProcessorName,
|
||||||
|
{ asset, fileName: asset.id },
|
||||||
|
{ jobId: randomUUID() },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return assetsWithNoExif.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async runMachineLearningPipeline(): Promise<number> {
|
||||||
|
const jobCount = await this.machineLearningQueue.getJobCounts();
|
||||||
|
|
||||||
|
if (jobCount.waiting > 0) {
|
||||||
|
throw new BadRequestException('Metadata extraction job is already running');
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetWithNoSmartInfo = await this._assetRepository.getAssetWithNoSmartInfo();
|
||||||
|
|
||||||
|
for (const asset of assetWithNoSmartInfo) {
|
||||||
|
await this.machineLearningQueue.add(MachineLearningJobNameEnum.IMAGE_TAGGING, { asset }, { jobId: randomUUID() });
|
||||||
|
await this.machineLearningQueue.add(
|
||||||
|
MachineLearningJobNameEnum.OBJECT_DETECTION,
|
||||||
|
{ asset },
|
||||||
|
{ jobId: randomUUID() },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return assetWithNoSmartInfo.length;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class JobCounts {
|
||||||
|
active!: number;
|
||||||
|
completed!: number;
|
||||||
|
failed!: number;
|
||||||
|
delayed!: number;
|
||||||
|
waiting!: number;
|
||||||
|
}
|
||||||
|
export class AllJobStatusResponseDto {
|
||||||
|
isThumbnailGenerationActive!: boolean;
|
||||||
|
isMetadataExtractionActive!: boolean;
|
||||||
|
isVideoConversionActive!: boolean;
|
||||||
|
isMachineLearningActive!: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: JobCounts,
|
||||||
|
})
|
||||||
|
thumbnailGenerationQueueCount!: JobCounts;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: JobCounts,
|
||||||
|
})
|
||||||
|
metadataExtractionQueueCount!: JobCounts;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: JobCounts,
|
||||||
|
})
|
||||||
|
videoConversionQueueCount!: JobCounts;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: JobCounts,
|
||||||
|
})
|
||||||
|
machineLearningQueueCount!: JobCounts;
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import Bull from 'bull';
|
||||||
|
|
||||||
|
export class JobStatusResponseDto {
|
||||||
|
isActive!: boolean;
|
||||||
|
queueCount!: Bull.JobCounts;
|
||||||
|
}
|
@ -5,13 +5,13 @@ export class ServerInfoResponseDto {
|
|||||||
diskUse!: string;
|
diskUse!: string;
|
||||||
diskAvailable!: string;
|
diskAvailable!: string;
|
||||||
|
|
||||||
@ApiProperty({ type: 'integer' })
|
@ApiProperty({ type: 'integer', format: 'int64' })
|
||||||
diskSizeRaw!: number;
|
diskSizeRaw!: number;
|
||||||
|
|
||||||
@ApiProperty({ type: 'integer' })
|
@ApiProperty({ type: 'integer', format: 'int64' })
|
||||||
diskUseRaw!: number;
|
diskUseRaw!: number;
|
||||||
|
|
||||||
@ApiProperty({ type: 'integer' })
|
@ApiProperty({ type: 'integer', format: 'int64' })
|
||||||
diskAvailableRaw!: number;
|
diskAvailableRaw!: number;
|
||||||
|
|
||||||
@ApiProperty({ type: 'number', format: 'float' })
|
@ApiProperty({ type: 'number', format: 'float' })
|
||||||
|
@ -15,6 +15,7 @@ import { AppController } from './app.controller';
|
|||||||
import { ScheduleModule } from '@nestjs/schedule';
|
import { ScheduleModule } from '@nestjs/schedule';
|
||||||
import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module';
|
import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module';
|
||||||
import { DatabaseModule } from '@app/database';
|
import { DatabaseModule } from '@app/database';
|
||||||
|
import { JobModule } from './api-v1/job/job.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -55,6 +56,8 @@ import { DatabaseModule } from '@app/database';
|
|||||||
ScheduleModule.forRoot(),
|
ScheduleModule.forRoot(),
|
||||||
|
|
||||||
ScheduleTasksModule,
|
ScheduleTasksModule,
|
||||||
|
|
||||||
|
JobModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
@ -3,18 +3,14 @@ import { Module } from '@nestjs/common';
|
|||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { AssetEntity } from '@app/database/entities/asset.entity';
|
import { AssetEntity } from '@app/database/entities/asset.entity';
|
||||||
import { ScheduleTasksService } from './schedule-tasks.service';
|
import { ScheduleTasksService } from './schedule-tasks.service';
|
||||||
import {
|
import { QueueNameEnum } from '@app/job/constants/queue-name.constant';
|
||||||
metadataExtractionQueueName,
|
|
||||||
thumbnailGeneratorQueueName,
|
|
||||||
videoConversionQueueName,
|
|
||||||
} from '@app/job/constants/queue-name.constant';
|
|
||||||
import { ExifEntity } from '@app/database/entities/exif.entity';
|
import { ExifEntity } from '@app/database/entities/exif.entity';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([AssetEntity, ExifEntity]),
|
TypeOrmModule.forFeature([AssetEntity, ExifEntity]),
|
||||||
BullModule.registerQueue({
|
BullModule.registerQueue({
|
||||||
name: videoConversionQueueName,
|
name: QueueNameEnum.VIDEO_CONVERSION,
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
@ -22,7 +18,7 @@ import { ExifEntity } from '@app/database/entities/exif.entity';
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
BullModule.registerQueue({
|
BullModule.registerQueue({
|
||||||
name: thumbnailGeneratorQueueName,
|
name: QueueNameEnum.THUMBNAIL_GENERATION,
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
@ -31,7 +27,7 @@ import { ExifEntity } from '@app/database/entities/exif.entity';
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
BullModule.registerQueue({
|
BullModule.registerQueue({
|
||||||
name: metadataExtractionQueueName,
|
name: QueueNameEnum.METADATA_EXTRACTION,
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
|
@ -12,11 +12,9 @@ import {
|
|||||||
generateWEBPThumbnailProcessorName,
|
generateWEBPThumbnailProcessorName,
|
||||||
IMetadataExtractionJob,
|
IMetadataExtractionJob,
|
||||||
IVideoTranscodeJob,
|
IVideoTranscodeJob,
|
||||||
metadataExtractionQueueName,
|
|
||||||
mp4ConversionProcessorName,
|
mp4ConversionProcessorName,
|
||||||
|
QueueNameEnum,
|
||||||
reverseGeocodingProcessorName,
|
reverseGeocodingProcessorName,
|
||||||
thumbnailGeneratorQueueName,
|
|
||||||
videoConversionQueueName,
|
|
||||||
videoMetadataExtractionProcessorName,
|
videoMetadataExtractionProcessorName,
|
||||||
} from '@app/job';
|
} from '@app/job';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
@ -30,13 +28,13 @@ export class ScheduleTasksService {
|
|||||||
@InjectRepository(ExifEntity)
|
@InjectRepository(ExifEntity)
|
||||||
private exifRepository: Repository<ExifEntity>,
|
private exifRepository: Repository<ExifEntity>,
|
||||||
|
|
||||||
@InjectQueue(thumbnailGeneratorQueueName)
|
@InjectQueue(QueueNameEnum.THUMBNAIL_GENERATION)
|
||||||
private thumbnailGeneratorQueue: Queue,
|
private thumbnailGeneratorQueue: Queue,
|
||||||
|
|
||||||
@InjectQueue(videoConversionQueueName)
|
@InjectQueue(QueueNameEnum.VIDEO_CONVERSION)
|
||||||
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
||||||
|
|
||||||
@InjectQueue(metadataExtractionQueueName)
|
@InjectQueue(QueueNameEnum.METADATA_EXTRACTION)
|
||||||
private metadataExtractionQueue: Queue<IMetadataExtractionJob>,
|
private metadataExtractionQueue: Queue<IMetadataExtractionJob>,
|
||||||
|
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
@ -108,11 +106,11 @@ export class ScheduleTasksService {
|
|||||||
|
|
||||||
@Cron(CronExpression.EVERY_DAY_AT_3AM)
|
@Cron(CronExpression.EVERY_DAY_AT_3AM)
|
||||||
async extractExif() {
|
async extractExif() {
|
||||||
const exifAssets = await this.assetRepository.find({
|
const exifAssets = await this.assetRepository
|
||||||
where: {
|
.createQueryBuilder('asset')
|
||||||
exifInfo: IsNull(),
|
.leftJoinAndSelect('asset.exifInfo', 'ei')
|
||||||
},
|
.where('ei."assetId" IS NULL')
|
||||||
});
|
.getMany();
|
||||||
|
|
||||||
for (const asset of exifAssets) {
|
for (const asset of exifAssets) {
|
||||||
if (asset.type === AssetType.VIDEO) {
|
if (asset.type === AssetType.VIDEO) {
|
||||||
|
@ -4,13 +4,7 @@ import { AssetEntity } from '@app/database/entities/asset.entity';
|
|||||||
import { ExifEntity } from '@app/database/entities/exif.entity';
|
import { ExifEntity } from '@app/database/entities/exif.entity';
|
||||||
import { SmartInfoEntity } from '@app/database/entities/smart-info.entity';
|
import { SmartInfoEntity } from '@app/database/entities/smart-info.entity';
|
||||||
import { UserEntity } from '@app/database/entities/user.entity';
|
import { UserEntity } from '@app/database/entities/user.entity';
|
||||||
import {
|
import { QueueNameEnum } from '@app/job/constants/queue-name.constant';
|
||||||
assetUploadedQueueName,
|
|
||||||
generateChecksumQueueName,
|
|
||||||
metadataExtractionQueueName,
|
|
||||||
thumbnailGeneratorQueueName,
|
|
||||||
videoConversionQueueName,
|
|
||||||
} from '@app/job/constants/queue-name.constant';
|
|
||||||
import { BullModule } from '@nestjs/bull';
|
import { BullModule } from '@nestjs/bull';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
@ -19,6 +13,7 @@ import { CommunicationModule } from '../../immich/src/api-v1/communication/commu
|
|||||||
import { MicroservicesService } from './microservices.service';
|
import { MicroservicesService } from './microservices.service';
|
||||||
import { AssetUploadedProcessor } from './processors/asset-uploaded.processor';
|
import { AssetUploadedProcessor } from './processors/asset-uploaded.processor';
|
||||||
import { GenerateChecksumProcessor } from './processors/generate-checksum.processor';
|
import { GenerateChecksumProcessor } from './processors/generate-checksum.processor';
|
||||||
|
import { MachineLearningProcessor } from './processors/machine-learning.processor';
|
||||||
import { MetadataExtractionProcessor } from './processors/metadata-extraction.processor';
|
import { MetadataExtractionProcessor } from './processors/metadata-extraction.processor';
|
||||||
import { ThumbnailGeneratorProcessor } from './processors/thumbnail.processor';
|
import { ThumbnailGeneratorProcessor } from './processors/thumbnail.processor';
|
||||||
import { VideoTranscodeProcessor } from './processors/video-transcode.processor';
|
import { VideoTranscodeProcessor } from './processors/video-transcode.processor';
|
||||||
@ -42,7 +37,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor'
|
|||||||
}),
|
}),
|
||||||
BullModule.registerQueue(
|
BullModule.registerQueue(
|
||||||
{
|
{
|
||||||
name: thumbnailGeneratorQueueName,
|
name: QueueNameEnum.THUMBNAIL_GENERATION,
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
@ -50,7 +45,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor'
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: assetUploadedQueueName,
|
name: QueueNameEnum.ASSET_UPLOADED,
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
@ -58,7 +53,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor'
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: metadataExtractionQueueName,
|
name: QueueNameEnum.METADATA_EXTRACTION,
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
@ -66,7 +61,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor'
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: videoConversionQueueName,
|
name: QueueNameEnum.VIDEO_CONVERSION,
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
@ -74,7 +69,15 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor'
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: generateChecksumQueueName,
|
name: QueueNameEnum.CHECKSUM_GENERATION,
|
||||||
|
defaultJobOptions: {
|
||||||
|
attempts: 3,
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: QueueNameEnum.MACHINE_LEARNING,
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
@ -92,6 +95,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor'
|
|||||||
MetadataExtractionProcessor,
|
MetadataExtractionProcessor,
|
||||||
VideoTranscodeProcessor,
|
VideoTranscodeProcessor,
|
||||||
GenerateChecksumProcessor,
|
GenerateChecksumProcessor,
|
||||||
|
MachineLearningProcessor,
|
||||||
ConfigService,
|
ConfigService,
|
||||||
],
|
],
|
||||||
exports: [],
|
exports: [],
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { generateChecksumQueueName } from '@app/job';
|
import { QueueNameEnum } from '@app/job';
|
||||||
import { InjectQueue } from '@nestjs/bull';
|
import { InjectQueue } from '@nestjs/bull';
|
||||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||||
import { Queue } from 'bull';
|
import { Queue } from 'bull';
|
||||||
@ -6,14 +6,18 @@ import { randomUUID } from 'node:crypto';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MicroservicesService implements OnModuleInit {
|
export class MicroservicesService implements OnModuleInit {
|
||||||
constructor (
|
constructor(
|
||||||
@InjectQueue(generateChecksumQueueName)
|
@InjectQueue(QueueNameEnum.CHECKSUM_GENERATION)
|
||||||
private generateChecksumQueue: Queue,
|
private generateChecksumQueue: Queue,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
await this.generateChecksumQueue.add({}, {
|
await this.generateChecksumQueue.add(
|
||||||
jobId: randomUUID(), delay: 10000 // wait for migration
|
{},
|
||||||
});
|
{
|
||||||
|
jobId: randomUUID(),
|
||||||
|
delay: 10000, // wait for migration
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,30 +4,27 @@ import {
|
|||||||
IMetadataExtractionJob,
|
IMetadataExtractionJob,
|
||||||
IThumbnailGenerationJob,
|
IThumbnailGenerationJob,
|
||||||
IVideoTranscodeJob,
|
IVideoTranscodeJob,
|
||||||
assetUploadedQueueName,
|
|
||||||
metadataExtractionQueueName,
|
|
||||||
thumbnailGeneratorQueueName,
|
|
||||||
videoConversionQueueName,
|
|
||||||
assetUploadedProcessorName,
|
assetUploadedProcessorName,
|
||||||
exifExtractionProcessorName,
|
exifExtractionProcessorName,
|
||||||
generateJPEGThumbnailProcessorName,
|
generateJPEGThumbnailProcessorName,
|
||||||
mp4ConversionProcessorName,
|
mp4ConversionProcessorName,
|
||||||
videoMetadataExtractionProcessorName,
|
videoMetadataExtractionProcessorName,
|
||||||
|
QueueNameEnum,
|
||||||
} from '@app/job';
|
} from '@app/job';
|
||||||
import { InjectQueue, Process, Processor } from '@nestjs/bull';
|
import { InjectQueue, Process, Processor } from '@nestjs/bull';
|
||||||
import { Job, Queue } from 'bull';
|
import { Job, Queue } from 'bull';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
|
|
||||||
@Processor(assetUploadedQueueName)
|
@Processor(QueueNameEnum.ASSET_UPLOADED)
|
||||||
export class AssetUploadedProcessor {
|
export class AssetUploadedProcessor {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectQueue(thumbnailGeneratorQueueName)
|
@InjectQueue(QueueNameEnum.THUMBNAIL_GENERATION)
|
||||||
private thumbnailGeneratorQueue: Queue<IThumbnailGenerationJob>,
|
private thumbnailGeneratorQueue: Queue<IThumbnailGenerationJob>,
|
||||||
|
|
||||||
@InjectQueue(metadataExtractionQueueName)
|
@InjectQueue(QueueNameEnum.METADATA_EXTRACTION)
|
||||||
private metadataExtractionQueue: Queue<IMetadataExtractionJob>,
|
private metadataExtractionQueue: Queue<IMetadataExtractionJob>,
|
||||||
|
|
||||||
@InjectQueue(videoConversionQueueName)
|
@InjectQueue(QueueNameEnum.VIDEO_CONVERSION)
|
||||||
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { AssetEntity } from '@app/database/entities/asset.entity';
|
import { AssetEntity } from '@app/database/entities/asset.entity';
|
||||||
import { generateChecksumQueueName } from '@app/job';
|
import { QueueNameEnum } from '@app/job';
|
||||||
import { Process, Processor } from '@nestjs/bull';
|
import { Process, Processor } from '@nestjs/bull';
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
@ -8,7 +8,7 @@ import fs from 'node:fs';
|
|||||||
import { FindOptionsWhere, IsNull, MoreThan, QueryFailedError, Repository } from 'typeorm';
|
import { FindOptionsWhere, IsNull, MoreThan, QueryFailedError, Repository } from 'typeorm';
|
||||||
|
|
||||||
// TODO: just temporary task to generate previous uploaded assets.
|
// TODO: just temporary task to generate previous uploaded assets.
|
||||||
@Processor(generateChecksumQueueName)
|
@Processor(QueueNameEnum.CHECKSUM_GENERATION)
|
||||||
export class GenerateChecksumProcessor {
|
export class GenerateChecksumProcessor {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(AssetEntity)
|
@InjectRepository(AssetEntity)
|
||||||
@ -33,7 +33,7 @@ export class GenerateChecksumProcessor {
|
|||||||
const assets = await this.assetRepository.find({
|
const assets = await this.assetRepository.find({
|
||||||
where: whereStat,
|
where: whereStat,
|
||||||
take: pageSize,
|
take: pageSize,
|
||||||
order: { id: 'ASC' }
|
order: { id: 'ASC' },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!assets?.length) {
|
if (!assets?.length) {
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
import { AssetEntity } from '@app/database/entities/asset.entity';
|
||||||
|
import { SmartInfoEntity } from '@app/database/entities/smart-info.entity';
|
||||||
|
import { MachineLearningJobNameEnum, QueueNameEnum } from '@app/job';
|
||||||
|
import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface';
|
||||||
|
import { Process, Processor } from '@nestjs/bull';
|
||||||
|
import { Logger } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { Job } from 'bull';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@Processor(QueueNameEnum.MACHINE_LEARNING)
|
||||||
|
export class MachineLearningProcessor {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(SmartInfoEntity)
|
||||||
|
private smartInfoRepository: Repository<SmartInfoEntity>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Process({ name: MachineLearningJobNameEnum.IMAGE_TAGGING, concurrency: 2 })
|
||||||
|
async tagImage(job: Job<IMachineLearningJob>) {
|
||||||
|
const { asset } = job.data;
|
||||||
|
|
||||||
|
const res = await axios.post('http://immich-machine-learning:3003/image-classifier/tag-image', {
|
||||||
|
thumbnailPath: asset.resizePath,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status == 201 && res.data.length > 0) {
|
||||||
|
const smartInfo = new SmartInfoEntity();
|
||||||
|
smartInfo.assetId = asset.id;
|
||||||
|
smartInfo.tags = [...res.data];
|
||||||
|
|
||||||
|
await this.smartInfoRepository.upsert(smartInfo, {
|
||||||
|
conflictPaths: ['assetId'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Process({ name: MachineLearningJobNameEnum.OBJECT_DETECTION, concurrency: 2 })
|
||||||
|
async detectObject(job: Job<IMachineLearningJob>) {
|
||||||
|
try {
|
||||||
|
const { asset }: { asset: AssetEntity } = job.data;
|
||||||
|
|
||||||
|
const res = await axios.post('http://immich-machine-learning:3003/object-detection/detect-object', {
|
||||||
|
thumbnailPath: asset.resizePath,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status == 201 && res.data.length > 0) {
|
||||||
|
const smartInfo = new SmartInfoEntity();
|
||||||
|
smartInfo.assetId = asset.id;
|
||||||
|
smartInfo.objects = [...res.data];
|
||||||
|
|
||||||
|
await this.smartInfoRepository.upsert(smartInfo, {
|
||||||
|
conflictPaths: ['assetId'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(`Failed to trigger object detection pipe line ${String(error)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +1,19 @@
|
|||||||
import { ImmichLogLevel } from '@app/common/constants/log-level.constant';
|
import { ImmichLogLevel } from '@app/common/constants/log-level.constant';
|
||||||
import { AssetEntity } from '@app/database/entities/asset.entity';
|
import { AssetEntity } from '@app/database/entities/asset.entity';
|
||||||
import { ExifEntity } from '@app/database/entities/exif.entity';
|
import { ExifEntity } from '@app/database/entities/exif.entity';
|
||||||
import { SmartInfoEntity } from '@app/database/entities/smart-info.entity';
|
|
||||||
import {
|
import {
|
||||||
IExifExtractionProcessor,
|
IExifExtractionProcessor,
|
||||||
IVideoLengthExtractionProcessor,
|
IVideoLengthExtractionProcessor,
|
||||||
exifExtractionProcessorName,
|
exifExtractionProcessorName,
|
||||||
imageTaggingProcessorName,
|
|
||||||
objectDetectionProcessorName,
|
|
||||||
videoMetadataExtractionProcessorName,
|
videoMetadataExtractionProcessorName,
|
||||||
metadataExtractionQueueName,
|
|
||||||
reverseGeocodingProcessorName,
|
reverseGeocodingProcessorName,
|
||||||
IReverseGeocodingProcessor,
|
IReverseGeocodingProcessor,
|
||||||
|
QueueNameEnum,
|
||||||
} from '@app/job';
|
} from '@app/job';
|
||||||
import { Process, Processor } from '@nestjs/bull';
|
import { Process, Processor } from '@nestjs/bull';
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import axios from 'axios';
|
|
||||||
import { Job } from 'bull';
|
import { Job } from 'bull';
|
||||||
import exifr from 'exifr';
|
import exifr from 'exifr';
|
||||||
import ffmpeg from 'fluent-ffmpeg';
|
import ffmpeg from 'fluent-ffmpeg';
|
||||||
@ -79,7 +75,7 @@ export interface GeoData {
|
|||||||
distance: number;
|
distance: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Processor(metadataExtractionQueueName)
|
@Processor(QueueNameEnum.METADATA_EXTRACTION)
|
||||||
export class MetadataExtractionProcessor {
|
export class MetadataExtractionProcessor {
|
||||||
private isGeocodeInitialized = false;
|
private isGeocodeInitialized = false;
|
||||||
private logLevel: ImmichLogLevel;
|
private logLevel: ImmichLogLevel;
|
||||||
@ -91,9 +87,6 @@ export class MetadataExtractionProcessor {
|
|||||||
@InjectRepository(ExifEntity)
|
@InjectRepository(ExifEntity)
|
||||||
private exifRepository: Repository<ExifEntity>,
|
private exifRepository: Repository<ExifEntity>,
|
||||||
|
|
||||||
@InjectRepository(SmartInfoEntity)
|
|
||||||
private smartInfoRepository: Repository<SmartInfoEntity>,
|
|
||||||
|
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
) {
|
) {
|
||||||
if (!configService.get('DISABLE_REVERSE_GEOCODING')) {
|
if (!configService.get('DISABLE_REVERSE_GEOCODING')) {
|
||||||
@ -109,7 +102,8 @@ export class MetadataExtractionProcessor {
|
|||||||
alternateNames: false,
|
alternateNames: false,
|
||||||
},
|
},
|
||||||
countries: [],
|
countries: [],
|
||||||
dumpDirectory: configService.get('REVERSE_GEOCODING_DUMP_DIRECTORY') || (process.cwd() + '/.reverse-geocoding-dump/'),
|
dumpDirectory:
|
||||||
|
configService.get('REVERSE_GEOCODING_DUMP_DIRECTORY') || process.cwd() + '/.reverse-geocoding-dump/',
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.isGeocodeInitialized = true;
|
this.isGeocodeInitialized = true;
|
||||||
Logger.log('Reverse Geocoding Initialised');
|
Logger.log('Reverse Geocoding Initialised');
|
||||||
@ -273,48 +267,6 @@ export class MetadataExtractionProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Process({ name: imageTaggingProcessorName, concurrency: 2 })
|
|
||||||
async tagImage(job: Job) {
|
|
||||||
const { asset }: { asset: AssetEntity } = job.data;
|
|
||||||
|
|
||||||
const res = await axios.post('http://immich-machine-learning:3003/image-classifier/tag-image', {
|
|
||||||
thumbnailPath: asset.resizePath,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status == 201 && res.data.length > 0) {
|
|
||||||
const smartInfo = new SmartInfoEntity();
|
|
||||||
smartInfo.assetId = asset.id;
|
|
||||||
smartInfo.tags = [...res.data];
|
|
||||||
|
|
||||||
await this.smartInfoRepository.upsert(smartInfo, {
|
|
||||||
conflictPaths: ['assetId'],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Process({ name: objectDetectionProcessorName, concurrency: 2 })
|
|
||||||
async detectObject(job: Job) {
|
|
||||||
try {
|
|
||||||
const { asset }: { asset: AssetEntity } = job.data;
|
|
||||||
|
|
||||||
const res = await axios.post('http://immich-machine-learning:3003/object-detection/detect-object', {
|
|
||||||
thumbnailPath: asset.resizePath,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status == 201 && res.data.length > 0) {
|
|
||||||
const smartInfo = new SmartInfoEntity();
|
|
||||||
smartInfo.assetId = asset.id;
|
|
||||||
smartInfo.objects = [...res.data];
|
|
||||||
|
|
||||||
await this.smartInfoRepository.upsert(smartInfo, {
|
|
||||||
conflictPaths: ['assetId'],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
Logger.error(`Failed to trigger object detection pipe line ${String(error)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Process({ name: videoMetadataExtractionProcessorName, concurrency: 2 })
|
@Process({ name: videoMetadataExtractionProcessorName, concurrency: 2 })
|
||||||
async extractVideoMetadata(job: Job<IVideoLengthExtractionProcessor>) {
|
async extractVideoMetadata(job: Job<IVideoLengthExtractionProcessor>) {
|
||||||
const { asset, fileName } = job.data;
|
const { asset, fileName } = job.data;
|
||||||
|
@ -5,11 +5,9 @@ import {
|
|||||||
WebpGeneratorProcessor,
|
WebpGeneratorProcessor,
|
||||||
generateJPEGThumbnailProcessorName,
|
generateJPEGThumbnailProcessorName,
|
||||||
generateWEBPThumbnailProcessorName,
|
generateWEBPThumbnailProcessorName,
|
||||||
imageTaggingProcessorName,
|
|
||||||
objectDetectionProcessorName,
|
|
||||||
metadataExtractionQueueName,
|
|
||||||
thumbnailGeneratorQueueName,
|
|
||||||
JpegGeneratorProcessor,
|
JpegGeneratorProcessor,
|
||||||
|
QueueNameEnum,
|
||||||
|
MachineLearningJobNameEnum,
|
||||||
} from '@app/job';
|
} from '@app/job';
|
||||||
import { InjectQueue, Process, Processor } from '@nestjs/bull';
|
import { InjectQueue, Process, Processor } from '@nestjs/bull';
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
@ -25,8 +23,9 @@ import sharp from 'sharp';
|
|||||||
import { Repository } from 'typeorm/repository/Repository';
|
import { Repository } from 'typeorm/repository/Repository';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { CommunicationGateway } from 'apps/immich/src/api-v1/communication/communication.gateway';
|
import { CommunicationGateway } from 'apps/immich/src/api-v1/communication/communication.gateway';
|
||||||
|
import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface';
|
||||||
|
|
||||||
@Processor(thumbnailGeneratorQueueName)
|
@Processor(QueueNameEnum.THUMBNAIL_GENERATION)
|
||||||
export class ThumbnailGeneratorProcessor {
|
export class ThumbnailGeneratorProcessor {
|
||||||
private logLevel: ImmichLogLevel;
|
private logLevel: ImmichLogLevel;
|
||||||
|
|
||||||
@ -34,13 +33,13 @@ export class ThumbnailGeneratorProcessor {
|
|||||||
@InjectRepository(AssetEntity)
|
@InjectRepository(AssetEntity)
|
||||||
private assetRepository: Repository<AssetEntity>,
|
private assetRepository: Repository<AssetEntity>,
|
||||||
|
|
||||||
@InjectQueue(thumbnailGeneratorQueueName)
|
@InjectQueue(QueueNameEnum.THUMBNAIL_GENERATION)
|
||||||
private thumbnailGeneratorQueue: Queue,
|
private thumbnailGeneratorQueue: Queue,
|
||||||
|
|
||||||
private wsCommunicationGateway: CommunicationGateway,
|
private wsCommunicationGateway: CommunicationGateway,
|
||||||
|
|
||||||
@InjectQueue(metadataExtractionQueueName)
|
@InjectQueue(QueueNameEnum.MACHINE_LEARNING)
|
||||||
private metadataExtractionQueue: Queue,
|
private machineLearningQueue: Queue<IMachineLearningJob>,
|
||||||
|
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
) {
|
) {
|
||||||
@ -80,8 +79,12 @@ export class ThumbnailGeneratorProcessor {
|
|||||||
asset.resizePath = jpegThumbnailPath;
|
asset.resizePath = jpegThumbnailPath;
|
||||||
|
|
||||||
await this.thumbnailGeneratorQueue.add(generateWEBPThumbnailProcessorName, { asset }, { jobId: randomUUID() });
|
await this.thumbnailGeneratorQueue.add(generateWEBPThumbnailProcessorName, { asset }, { jobId: randomUUID() });
|
||||||
await this.metadataExtractionQueue.add(imageTaggingProcessorName, { asset }, { jobId: randomUUID() });
|
await this.machineLearningQueue.add(MachineLearningJobNameEnum.IMAGE_TAGGING, { asset }, { jobId: randomUUID() });
|
||||||
await this.metadataExtractionQueue.add(objectDetectionProcessorName, { asset }, { jobId: randomUUID() });
|
await this.machineLearningQueue.add(
|
||||||
|
MachineLearningJobNameEnum.OBJECT_DETECTION,
|
||||||
|
{ asset },
|
||||||
|
{ jobId: randomUUID() },
|
||||||
|
);
|
||||||
this.wsCommunicationGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(mapAsset(asset)));
|
this.wsCommunicationGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(mapAsset(asset)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,8 +113,12 @@ export class ThumbnailGeneratorProcessor {
|
|||||||
asset.resizePath = jpegThumbnailPath;
|
asset.resizePath = jpegThumbnailPath;
|
||||||
|
|
||||||
await this.thumbnailGeneratorQueue.add(generateWEBPThumbnailProcessorName, { asset }, { jobId: randomUUID() });
|
await this.thumbnailGeneratorQueue.add(generateWEBPThumbnailProcessorName, { asset }, { jobId: randomUUID() });
|
||||||
await this.metadataExtractionQueue.add(imageTaggingProcessorName, { asset }, { jobId: randomUUID() });
|
await this.machineLearningQueue.add(MachineLearningJobNameEnum.IMAGE_TAGGING, { asset }, { jobId: randomUUID() });
|
||||||
await this.metadataExtractionQueue.add(objectDetectionProcessorName, { asset }, { jobId: randomUUID() });
|
await this.machineLearningQueue.add(
|
||||||
|
MachineLearningJobNameEnum.OBJECT_DETECTION,
|
||||||
|
{ asset },
|
||||||
|
{ jobId: randomUUID() },
|
||||||
|
);
|
||||||
|
|
||||||
this.wsCommunicationGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(mapAsset(asset)));
|
this.wsCommunicationGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(mapAsset(asset)));
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { APP_UPLOAD_LOCATION } from '@app/common/constants';
|
import { APP_UPLOAD_LOCATION } from '@app/common/constants';
|
||||||
import { AssetEntity } from '@app/database/entities/asset.entity';
|
import { AssetEntity } from '@app/database/entities/asset.entity';
|
||||||
|
import { QueueNameEnum } from '@app/job';
|
||||||
import { mp4ConversionProcessorName } from '@app/job/constants/job-name.constant';
|
import { mp4ConversionProcessorName } from '@app/job/constants/job-name.constant';
|
||||||
import { videoConversionQueueName } from '@app/job/constants/queue-name.constant';
|
|
||||||
import { IMp4ConversionProcessor } from '@app/job/interfaces/video-transcode.interface';
|
import { IMp4ConversionProcessor } from '@app/job/interfaces/video-transcode.interface';
|
||||||
import { Process, Processor } from '@nestjs/bull';
|
import { Process, Processor } from '@nestjs/bull';
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
@ -11,7 +11,7 @@ import ffmpeg from 'fluent-ffmpeg';
|
|||||||
import { existsSync, mkdirSync } from 'fs';
|
import { existsSync, mkdirSync } from 'fs';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
@Processor(videoConversionQueueName)
|
@Processor(QueueNameEnum.VIDEO_CONVERSION)
|
||||||
export class VideoTranscodeProcessor {
|
export class VideoTranscodeProcessor {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(AssetEntity)
|
@InjectRepository(AssetEntity)
|
||||||
|
File diff suppressed because one or more lines are too long
@ -20,5 +20,12 @@ export const generateWEBPThumbnailProcessorName = 'generate-webp-thumbnail';
|
|||||||
export const exifExtractionProcessorName = 'exif-extraction';
|
export const exifExtractionProcessorName = 'exif-extraction';
|
||||||
export const videoMetadataExtractionProcessorName = 'extract-video-metadata';
|
export const videoMetadataExtractionProcessorName = 'extract-video-metadata';
|
||||||
export const reverseGeocodingProcessorName = 'reverse-geocoding';
|
export const reverseGeocodingProcessorName = 'reverse-geocoding';
|
||||||
export const objectDetectionProcessorName = 'detect-object';
|
|
||||||
export const imageTaggingProcessorName = 'tag-image';
|
/**
|
||||||
|
* Machine learning Queue Jobs
|
||||||
|
*/
|
||||||
|
|
||||||
|
export enum MachineLearningJobNameEnum {
|
||||||
|
OBJECT_DETECTION = 'detect-object',
|
||||||
|
IMAGE_TAGGING = 'tag-image',
|
||||||
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
export const thumbnailGeneratorQueueName = 'thumbnail-generator-queue';
|
export enum QueueNameEnum {
|
||||||
export const assetUploadedQueueName = 'asset-uploaded-queue';
|
THUMBNAIL_GENERATION = 'thumbnail-generation-queue',
|
||||||
export const metadataExtractionQueueName = 'metadata-extraction-queue';
|
METADATA_EXTRACTION = 'metadata-extraction-queue',
|
||||||
export const videoConversionQueueName = 'video-conversion-queue';
|
VIDEO_CONVERSION = 'video-conversion-queue',
|
||||||
export const generateChecksumQueueName = 'generate-checksum-queue';
|
CHECKSUM_GENERATION = 'generate-checksum-queue',
|
||||||
|
ASSET_UPLOADED = 'asset-uploaded-queue',
|
||||||
|
MACHINE_LEARNING = 'machine-learning-queue',
|
||||||
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
import { AssetEntity } from '@app/database/entities/asset.entity';
|
||||||
|
|
||||||
|
export interface IMachineLearningJob {
|
||||||
|
/**
|
||||||
|
* The Asset entity that was saved in the database
|
||||||
|
*/
|
||||||
|
asset: AssetEntity;
|
||||||
|
}
|
69
server/package-lock.json
generated
69
server/package-lock.json
generated
@ -59,7 +59,7 @@
|
|||||||
"@nestjs/testing": "^8.4.7",
|
"@nestjs/testing": "^8.4.7",
|
||||||
"@openapitools/openapi-generator-cli": "2.5.1",
|
"@openapitools/openapi-generator-cli": "2.5.1",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/bull": "^3.15.7",
|
"@types/bull": "^3.15.9",
|
||||||
"@types/cookie-parser": "^1.4.3",
|
"@types/cookie-parser": "^1.4.3",
|
||||||
"@types/cron": "^2.0.0",
|
"@types/cron": "^2.0.0",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
@ -2339,9 +2339,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/bull": {
|
"node_modules/@types/bull": {
|
||||||
"version": "3.15.7",
|
"version": "3.15.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/bull/-/bull-3.15.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/bull/-/bull-3.15.9.tgz",
|
||||||
"integrity": "sha512-7NC7XN5NoS0A+leJ/dR69ZfKaegOlCZaii/xGgKnCyh1UYisRncibImb7VMwrc3OdJcbDJt6+4om70TeNl3J7g==",
|
"integrity": "sha512-MPUcyPPQauAmynoO3ezHAmCOhbB0pWmYyijr/5ctaCqhbKWsjW0YCod38ZcLzUBprosfZ9dPqfYIcfdKjk7RNQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/ioredis": "*",
|
"@types/ioredis": "*",
|
||||||
@ -3764,6 +3764,27 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cache-manager": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-1qKdoeoJKmrf95Zvhr3NpBVAgBESt4TuZomBzn4N2gCFZvHjuUXBK1H8EDVsJdba6/grIgi6WGYb/ncJj+wjtg==",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"lru-cache": "^7.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cache-manager/node_modules/lru-cache": {
|
||||||
|
"version": "7.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.0.tgz",
|
||||||
|
"integrity": "sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/call-bind": {
|
"node_modules/call-bind": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||||
@ -7674,6 +7695,13 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.clonedeep": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/lodash.defaults": {
|
"node_modules/lodash.defaults": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||||
@ -12900,9 +12928,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/bull": {
|
"@types/bull": {
|
||||||
"version": "3.15.7",
|
"version": "3.15.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/bull/-/bull-3.15.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/bull/-/bull-3.15.9.tgz",
|
||||||
"integrity": "sha512-7NC7XN5NoS0A+leJ/dR69ZfKaegOlCZaii/xGgKnCyh1UYisRncibImb7VMwrc3OdJcbDJt6+4om70TeNl3J7g==",
|
"integrity": "sha512-MPUcyPPQauAmynoO3ezHAmCOhbB0pWmYyijr/5ctaCqhbKWsjW0YCod38ZcLzUBprosfZ9dPqfYIcfdKjk7RNQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/ioredis": "*",
|
"@types/ioredis": "*",
|
||||||
@ -14073,6 +14101,26 @@
|
|||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
|
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
|
||||||
},
|
},
|
||||||
|
"cache-manager": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-1qKdoeoJKmrf95Zvhr3NpBVAgBESt4TuZomBzn4N2gCFZvHjuUXBK1H8EDVsJdba6/grIgi6WGYb/ncJj+wjtg==",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"requires": {
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"lru-cache": "^7.14.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": {
|
||||||
|
"version": "7.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.0.tgz",
|
||||||
|
"integrity": "sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"call-bind": {
|
"call-bind": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||||
@ -17088,6 +17136,13 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
},
|
},
|
||||||
|
"lodash.clonedeep": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"lodash.defaults": {
|
"lodash.defaults": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||||
|
@ -78,7 +78,7 @@
|
|||||||
"@nestjs/testing": "^8.4.7",
|
"@nestjs/testing": "^8.4.7",
|
||||||
"@openapitools/openapi-generator-cli": "2.5.1",
|
"@openapitools/openapi-generator-cli": "2.5.1",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/bull": "^3.15.7",
|
"@types/bull": "^3.15.9",
|
||||||
"@types/cookie-parser": "^1.4.3",
|
"@types/cookie-parser": "^1.4.3",
|
||||||
"@types/cron": "^2.0.0",
|
"@types/cron": "^2.0.0",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
AuthenticationApi,
|
AuthenticationApi,
|
||||||
Configuration,
|
Configuration,
|
||||||
DeviceInfoApi,
|
DeviceInfoApi,
|
||||||
|
JobApi,
|
||||||
ServerInfoApi,
|
ServerInfoApi,
|
||||||
UserApi
|
UserApi
|
||||||
} from './open-api';
|
} from './open-api';
|
||||||
@ -15,6 +16,8 @@ class ImmichApi {
|
|||||||
public authenticationApi: AuthenticationApi;
|
public authenticationApi: AuthenticationApi;
|
||||||
public deviceInfoApi: DeviceInfoApi;
|
public deviceInfoApi: DeviceInfoApi;
|
||||||
public serverInfoApi: ServerInfoApi;
|
public serverInfoApi: ServerInfoApi;
|
||||||
|
public jobApi: JobApi;
|
||||||
|
|
||||||
private config = new Configuration({ basePath: '/api' });
|
private config = new Configuration({ basePath: '/api' });
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -24,6 +27,7 @@ class ImmichApi {
|
|||||||
this.authenticationApi = new AuthenticationApi(this.config);
|
this.authenticationApi = new AuthenticationApi(this.config);
|
||||||
this.deviceInfoApi = new DeviceInfoApi(this.config);
|
this.deviceInfoApi = new DeviceInfoApi(this.config);
|
||||||
this.serverInfoApi = new ServerInfoApi(this.config);
|
this.serverInfoApi = new ServerInfoApi(this.config);
|
||||||
|
this.jobApi = new JobApi(this.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
public setAccessToken(accessToken: string) {
|
public setAccessToken(accessToken: string) {
|
||||||
|
@ -170,6 +170,61 @@ export interface AlbumResponseDto {
|
|||||||
*/
|
*/
|
||||||
'assets': Array<AssetResponseDto>;
|
'assets': Array<AssetResponseDto>;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface AllJobStatusResponseDto
|
||||||
|
*/
|
||||||
|
export interface AllJobStatusResponseDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {JobCounts}
|
||||||
|
* @memberof AllJobStatusResponseDto
|
||||||
|
*/
|
||||||
|
'thumbnailGenerationQueueCount': JobCounts;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {JobCounts}
|
||||||
|
* @memberof AllJobStatusResponseDto
|
||||||
|
*/
|
||||||
|
'metadataExtractionQueueCount': JobCounts;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {JobCounts}
|
||||||
|
* @memberof AllJobStatusResponseDto
|
||||||
|
*/
|
||||||
|
'videoConversionQueueCount': JobCounts;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {JobCounts}
|
||||||
|
* @memberof AllJobStatusResponseDto
|
||||||
|
*/
|
||||||
|
'machineLearningQueueCount': JobCounts;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof AllJobStatusResponseDto
|
||||||
|
*/
|
||||||
|
'isThumbnailGenerationActive': boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof AllJobStatusResponseDto
|
||||||
|
*/
|
||||||
|
'isMetadataExtractionActive': boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof AllJobStatusResponseDto
|
||||||
|
*/
|
||||||
|
'isVideoConversionActive': boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof AllJobStatusResponseDto
|
||||||
|
*/
|
||||||
|
'isMachineLearningActive': boolean;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@ -683,10 +738,16 @@ export type DeviceTypeEnum = typeof DeviceTypeEnum[keyof typeof DeviceTypeEnum];
|
|||||||
export interface ExifResponseDto {
|
export interface ExifResponseDto {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {number}
|
||||||
* @memberof ExifResponseDto
|
* @memberof ExifResponseDto
|
||||||
*/
|
*/
|
||||||
'id'?: string | null;
|
'id'?: number | null;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof ExifResponseDto
|
||||||
|
*/
|
||||||
|
'fileSizeInByte'?: number | null;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
@ -717,12 +778,6 @@ export interface ExifResponseDto {
|
|||||||
* @memberof ExifResponseDto
|
* @memberof ExifResponseDto
|
||||||
*/
|
*/
|
||||||
'exifImageHeight'?: number | null;
|
'exifImageHeight'?: number | null;
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {number}
|
|
||||||
* @memberof ExifResponseDto
|
|
||||||
*/
|
|
||||||
'fileSizeInByte'?: number | null;
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
@ -828,6 +883,105 @@ export interface GetAssetCountByTimeBucketDto {
|
|||||||
*/
|
*/
|
||||||
'timeGroup': TimeGroupEnum;
|
'timeGroup': TimeGroupEnum;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const JobCommand = {
|
||||||
|
Start: 'start',
|
||||||
|
Stop: 'stop'
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type JobCommand = typeof JobCommand[keyof typeof JobCommand];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface JobCommandDto
|
||||||
|
*/
|
||||||
|
export interface JobCommandDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {JobCommand}
|
||||||
|
* @memberof JobCommandDto
|
||||||
|
*/
|
||||||
|
'command': JobCommand;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface JobCounts
|
||||||
|
*/
|
||||||
|
export interface JobCounts {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof JobCounts
|
||||||
|
*/
|
||||||
|
'active': number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof JobCounts
|
||||||
|
*/
|
||||||
|
'completed': number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof JobCounts
|
||||||
|
*/
|
||||||
|
'failed': number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof JobCounts
|
||||||
|
*/
|
||||||
|
'delayed': number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof JobCounts
|
||||||
|
*/
|
||||||
|
'waiting': number;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const JobId = {
|
||||||
|
ThumbnailGeneration: 'thumbnail-generation',
|
||||||
|
MetadataExtraction: 'metadata-extraction',
|
||||||
|
VideoConversion: 'video-conversion',
|
||||||
|
MachineLearning: 'machine-learning'
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type JobId = typeof JobId[keyof typeof JobId];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface JobStatusResponseDto
|
||||||
|
*/
|
||||||
|
export interface JobStatusResponseDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof JobStatusResponseDto
|
||||||
|
*/
|
||||||
|
'isActive': boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {object}
|
||||||
|
* @memberof JobStatusResponseDto
|
||||||
|
*/
|
||||||
|
'queueCount': object;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@ -3682,6 +3836,247 @@ export class DeviceInfoApi extends BaseAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JobApi - axios parameter creator
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const JobApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getAllJobsStatus: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/jobs`;
|
||||||
|
// 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: 'GET', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
// 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 {JobId} jobId
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getJobStatus: async (jobId: JobId, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
// verify required parameter 'jobId' is not null or undefined
|
||||||
|
assertParamExists('getJobStatus', 'jobId', jobId)
|
||||||
|
const localVarPath = `/jobs/{jobId}`
|
||||||
|
.replace(`{${"jobId"}}`, encodeURIComponent(String(jobId)));
|
||||||
|
// 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: 'GET', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
// 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 {JobId} jobId
|
||||||
|
* @param {JobCommandDto} jobCommandDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
sendJobCommand: async (jobId: JobId, jobCommandDto: JobCommandDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
// verify required parameter 'jobId' is not null or undefined
|
||||||
|
assertParamExists('sendJobCommand', 'jobId', jobId)
|
||||||
|
// verify required parameter 'jobCommandDto' is not null or undefined
|
||||||
|
assertParamExists('sendJobCommand', 'jobCommandDto', jobCommandDto)
|
||||||
|
const localVarPath = `/jobs/{jobId}`
|
||||||
|
.replace(`{${"jobId"}}`, encodeURIComponent(String(jobId)));
|
||||||
|
// 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: 'PUT', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
// 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(jobCommandDto, localVarRequestOptions, configuration)
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JobApi - functional programming interface
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const JobApiFp = function(configuration?: Configuration) {
|
||||||
|
const localVarAxiosParamCreator = JobApiAxiosParamCreator(configuration)
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async getAllJobsStatus(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AllJobStatusResponseDto>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getAllJobsStatus(options);
|
||||||
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {JobId} jobId
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async getJobStatus(jobId: JobId, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<JobStatusResponseDto>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getJobStatus(jobId, options);
|
||||||
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {JobId} jobId
|
||||||
|
* @param {JobCommandDto} jobCommandDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async sendJobCommand(jobId: JobId, jobCommandDto: JobCommandDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<number>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.sendJobCommand(jobId, jobCommandDto, options);
|
||||||
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JobApi - factory interface
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const JobApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||||
|
const localVarFp = JobApiFp(configuration)
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getAllJobsStatus(options?: any): AxiosPromise<AllJobStatusResponseDto> {
|
||||||
|
return localVarFp.getAllJobsStatus(options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {JobId} jobId
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getJobStatus(jobId: JobId, options?: any): AxiosPromise<JobStatusResponseDto> {
|
||||||
|
return localVarFp.getJobStatus(jobId, options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {JobId} jobId
|
||||||
|
* @param {JobCommandDto} jobCommandDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
sendJobCommand(jobId: JobId, jobCommandDto: JobCommandDto, options?: any): AxiosPromise<number> {
|
||||||
|
return localVarFp.sendJobCommand(jobId, jobCommandDto, options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JobApi - object-oriented interface
|
||||||
|
* @export
|
||||||
|
* @class JobApi
|
||||||
|
* @extends {BaseAPI}
|
||||||
|
*/
|
||||||
|
export class JobApi extends BaseAPI {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof JobApi
|
||||||
|
*/
|
||||||
|
public getAllJobsStatus(options?: AxiosRequestConfig) {
|
||||||
|
return JobApiFp(this.configuration).getAllJobsStatus(options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {JobId} jobId
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof JobApi
|
||||||
|
*/
|
||||||
|
public getJobStatus(jobId: JobId, options?: AxiosRequestConfig) {
|
||||||
|
return JobApiFp(this.configuration).getJobStatus(jobId, options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {JobId} jobId
|
||||||
|
* @param {JobCommandDto} jobCommandDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof JobApi
|
||||||
|
*/
|
||||||
|
public sendJobCommand(jobId: JobId, jobCommandDto: JobCommandDto, options?: AxiosRequestConfig) {
|
||||||
|
return JobApiFp(this.configuration).sendJobCommand(jobId, jobCommandDto, options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ServerInfoApi - axios parameter creator
|
* ServerInfoApi - axios parameter creator
|
||||||
* @export
|
* @export
|
||||||
|
52
web/src/lib/components/admin-page/jobs/job-tile.svelte
Normal file
52
web/src/lib/components/admin-page/jobs/job-tile.svelte
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
export let title: string;
|
||||||
|
export let subtitle: string;
|
||||||
|
export let buttonTitle = 'Run';
|
||||||
|
export let jobStatus: boolean;
|
||||||
|
export let waitingJobCount: number;
|
||||||
|
export let activeJobCount: number;
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex border p-6 rounded-2xl bg-white">
|
||||||
|
<div class="w-[70%]">
|
||||||
|
<h1 class="font-medium text-immich-primary">{title}</h1>
|
||||||
|
<p class="text-sm mt-1 font-medium">{subtitle}</p>
|
||||||
|
<p class="text-sm">
|
||||||
|
<slot />
|
||||||
|
</p>
|
||||||
|
<table class="text-left w-full mt-4">
|
||||||
|
<!-- table header -->
|
||||||
|
<thead class="border rounded-md mb-2 bg-gray-50 flex text-immich-primary w-full h-12">
|
||||||
|
<tr class="flex w-full place-items-center">
|
||||||
|
<th class="text-center w-1/3 font-medium text-sm">Status</th>
|
||||||
|
<th class="text-center w-1/3 font-medium text-sm">Active</th>
|
||||||
|
<th class="text-center w-1/3 font-medium text-sm">Waiting</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="overflow-y-auto rounded-md w-full max-h-[320px] block border">
|
||||||
|
<tr class="text-center flex place-items-center w-full h-[40px]">
|
||||||
|
<td class="text-sm px-2 w-1/3 text-ellipsis">{jobStatus ? 'Active' : 'Idle'}</td>
|
||||||
|
<td class="text-sm px-2 w-1/3 text-ellipsis">{activeJobCount}</td>
|
||||||
|
<td class="text-sm px-2 w-1/3 text-ellipsis">{waitingJobCount}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="w-[30%] flex place-items-center place-content-end">
|
||||||
|
<button
|
||||||
|
on:click={() => dispatch('click')}
|
||||||
|
class="border px-6 py-3 text-sm bg-gray-50 font-medium rounded-2xl hover:bg-immich-primary/10 transition-all hover:cursor-pointer disabled:cursor-not-allowed"
|
||||||
|
disabled={jobStatus}
|
||||||
|
>
|
||||||
|
{#if jobStatus}
|
||||||
|
<LoadingSpinner />
|
||||||
|
{:else}
|
||||||
|
{buttonTitle}
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
138
web/src/lib/components/admin-page/jobs/jobs-panel.svelte
Normal file
138
web/src/lib/components/admin-page/jobs/jobs-panel.svelte
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
notificationController,
|
||||||
|
NotificationType
|
||||||
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
|
import { AllJobStatusResponseDto, api, JobCommand, JobId } from '@api';
|
||||||
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
import JobTile from './job-tile.svelte';
|
||||||
|
|
||||||
|
let allJobsStatus: AllJobStatusResponseDto;
|
||||||
|
let setIntervalHandler: NodeJS.Timer;
|
||||||
|
onMount(async () => {
|
||||||
|
const { data } = await api.jobApi.getAllJobsStatus();
|
||||||
|
allJobsStatus = data;
|
||||||
|
|
||||||
|
setIntervalHandler = setInterval(async () => {
|
||||||
|
const { data } = await api.jobApi.getAllJobsStatus();
|
||||||
|
allJobsStatus = data;
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
1;
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
clearInterval(setIntervalHandler);
|
||||||
|
});
|
||||||
|
|
||||||
|
const runThumbnailGeneration = async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await api.jobApi.sendJobCommand(JobId.ThumbnailGeneration, {
|
||||||
|
command: JobCommand.Start
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
notificationController.show({
|
||||||
|
message: `Thumbnail generation job started for ${data} asset`,
|
||||||
|
type: NotificationType.Info
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notificationController.show({
|
||||||
|
message: `No missing thumbnails found`,
|
||||||
|
type: NotificationType.Info
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('[ERROR] runThumbnailGeneration', e);
|
||||||
|
|
||||||
|
notificationController.show({
|
||||||
|
message: `Error running thumbnail generation job, check console for more detail`,
|
||||||
|
type: NotificationType.Error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const runExtractEXIF = async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await api.jobApi.sendJobCommand(JobId.MetadataExtraction, {
|
||||||
|
command: JobCommand.Start
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
notificationController.show({
|
||||||
|
message: `Extract EXIF job started for ${data} asset`,
|
||||||
|
type: NotificationType.Info
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notificationController.show({
|
||||||
|
message: `No missing EXIF found`,
|
||||||
|
type: NotificationType.Info
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('[ERROR] runExtractEXIF', e);
|
||||||
|
|
||||||
|
notificationController.show({
|
||||||
|
message: `Error running extract EXIF job, check console for more detail`,
|
||||||
|
type: NotificationType.Error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const runMachineLearning = async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await api.jobApi.sendJobCommand(JobId.MachineLearning, {
|
||||||
|
command: JobCommand.Start
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
notificationController.show({
|
||||||
|
message: `Object detection job started for ${data} asset`,
|
||||||
|
type: NotificationType.Info
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notificationController.show({
|
||||||
|
message: `No missing object detection found`,
|
||||||
|
type: NotificationType.Info
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('[ERROR] runMachineLearning', e);
|
||||||
|
|
||||||
|
notificationController.show({
|
||||||
|
message: `Error running machine learning job, check console for more detail`,
|
||||||
|
type: NotificationType.Error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-6">
|
||||||
|
<JobTile
|
||||||
|
title={'Generate thumbnails'}
|
||||||
|
subtitle={'Regenerate missing thumbnail (JPEG, WEBP)'}
|
||||||
|
on:click={runThumbnailGeneration}
|
||||||
|
jobStatus={allJobsStatus?.isThumbnailGenerationActive}
|
||||||
|
waitingJobCount={allJobsStatus?.thumbnailGenerationQueueCount.waiting}
|
||||||
|
activeJobCount={allJobsStatus?.thumbnailGenerationQueueCount.active}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<JobTile
|
||||||
|
title={'Extract EXIF'}
|
||||||
|
subtitle={'Extract missing EXIF information'}
|
||||||
|
on:click={runExtractEXIF}
|
||||||
|
jobStatus={allJobsStatus?.isMetadataExtractionActive}
|
||||||
|
waitingJobCount={allJobsStatus?.metadataExtractionQueueCount.waiting}
|
||||||
|
activeJobCount={allJobsStatus?.metadataExtractionQueueCount.active}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<JobTile
|
||||||
|
title={'Detect objects'}
|
||||||
|
subtitle={'Run machine learning process to detect and classify objects'}
|
||||||
|
on:click={runMachineLearning}
|
||||||
|
jobStatus={allJobsStatus?.isMachineLearningActive}
|
||||||
|
waitingJobCount={allJobsStatus?.machineLearningQueueCount.waiting}
|
||||||
|
activeJobCount={allJobsStatus?.machineLearningQueueCount.active}
|
||||||
|
>
|
||||||
|
Note that some asset does not have any object detected, this is normal.
|
||||||
|
</JobTile>
|
||||||
|
</div>
|
@ -94,7 +94,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
id="immich-scrubbable-scrollbar"
|
id="immich-scrubbable-scrollbar"
|
||||||
class="fixed right-0 bg-immich-bg z-10 hover:cursor-row-resize select-none"
|
class="fixed right-0 bg-immich-bg z-[999] hover:cursor-row-resize select-none "
|
||||||
style:width={isDragging ? '100vw' : '60px'}
|
style:width={isDragging ? '100vw' : '60px'}
|
||||||
style:background-color={isDragging ? 'transparent' : 'transparent'}
|
style:background-color={isDragging ? 'transparent' : 'transparent'}
|
||||||
on:mouseenter={() => (isHover = true)}
|
on:mouseenter={() => (isHover = true)}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
export enum AdminSideBarSelection {
|
export enum AdminSideBarSelection {
|
||||||
USER_MANAGEMENT = 'User management'
|
USER_MANAGEMENT = 'User management',
|
||||||
|
JOBS = 'Jobs',
|
||||||
|
SETTINGS = 'Settings'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AppSideBarSelection {
|
export enum AppSideBarSelection {
|
||||||
|
3
web/src/routes/admin/+layout.svelte
Normal file
3
web/src/routes/admin/+layout.svelte
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<main>
|
||||||
|
<slot />
|
||||||
|
</main>
|
@ -4,6 +4,7 @@
|
|||||||
import { AdminSideBarSelection } from '$lib/models/admin-sidebar-selection';
|
import { AdminSideBarSelection } from '$lib/models/admin-sidebar-selection';
|
||||||
import SideBarButton from '$lib/components/shared-components/side-bar/side-bar-button.svelte';
|
import SideBarButton from '$lib/components/shared-components/side-bar/side-bar-button.svelte';
|
||||||
import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
|
import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
|
||||||
|
import Cog from 'svelte-material-icons/Cog.svelte';
|
||||||
import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
|
import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
|
||||||
import UserManagement from '$lib/components/admin-page/user-management.svelte';
|
import UserManagement from '$lib/components/admin-page/user-management.svelte';
|
||||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||||
@ -12,6 +13,7 @@
|
|||||||
import StatusBox from '$lib/components/shared-components/status-box.svelte';
|
import StatusBox from '$lib/components/shared-components/status-box.svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { api, UserResponseDto } from '@api';
|
import { api, UserResponseDto } from '@api';
|
||||||
|
import JobsPanel from '$lib/components/admin-page/jobs/jobs-panel.svelte';
|
||||||
|
|
||||||
let selectedAction: AdminSideBarSelection = AdminSideBarSelection.USER_MANAGEMENT;
|
let selectedAction: AdminSideBarSelection = AdminSideBarSelection.USER_MANAGEMENT;
|
||||||
|
|
||||||
@ -104,14 +106,21 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen">
|
<section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen">
|
||||||
<section id="admin-sidebar" class="pt-8 pr-6 flex flex-col">
|
<section id="admin-sidebar" class="pt-8 pr-6 flex flex-col gap-1">
|
||||||
<SideBarButton
|
<SideBarButton
|
||||||
title="User"
|
title="Users"
|
||||||
logo={AccountMultipleOutline}
|
logo={AccountMultipleOutline}
|
||||||
actionType={AdminSideBarSelection.USER_MANAGEMENT}
|
actionType={AdminSideBarSelection.USER_MANAGEMENT}
|
||||||
isSelected={selectedAction === AdminSideBarSelection.USER_MANAGEMENT}
|
isSelected={selectedAction === AdminSideBarSelection.USER_MANAGEMENT}
|
||||||
on:selected={onButtonClicked}
|
on:selected={onButtonClicked}
|
||||||
/>
|
/>
|
||||||
|
<SideBarButton
|
||||||
|
title="Jobs"
|
||||||
|
logo={Cog}
|
||||||
|
actionType={AdminSideBarSelection.JOBS}
|
||||||
|
isSelected={selectedAction === AdminSideBarSelection.JOBS}
|
||||||
|
on:selected={onButtonClicked}
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="mb-6 mt-auto">
|
<div class="mb-6 mt-auto">
|
||||||
<StatusBox />
|
<StatusBox />
|
||||||
@ -132,6 +141,9 @@
|
|||||||
on:edit-user={editUserHandler}
|
on:edit-user={editUserHandler}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if selectedAction === AdminSideBarSelection.JOBS}
|
||||||
|
<JobsPanel />
|
||||||
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user