feat: queues (#24142)

This commit is contained in:
Jason Rasmussen
2025-11-25 08:19:40 -05:00
committed by GitHub
parent 66ae07ee39
commit 104fa09f69
37 changed files with 2487 additions and 336 deletions

View File

@@ -12,7 +12,7 @@ import {
PersonCreateDto,
QueueCommandDto,
QueueName,
QueuesResponseDto,
QueuesResponseLegacyDto,
SharedLinkCreateDto,
UpdateLibraryDto,
UserAdminCreateDto,
@@ -564,13 +564,13 @@ export const utils = {
await updateConfig({ systemConfigDto: defaultConfig }, { headers: asBearerAuth(accessToken) });
},
isQueueEmpty: async (accessToken: string, queue: keyof QueuesResponseDto) => {
isQueueEmpty: async (accessToken: string, queue: keyof QueuesResponseLegacyDto) => {
const queues = await getQueuesLegacy({ headers: asBearerAuth(accessToken) });
const jobCounts = queues[queue].jobCounts;
return !jobCounts.active && !jobCounts.waiting;
},
waitForQueueFinish: (accessToken: string, queue: keyof QueuesResponseDto, ms?: number) => {
waitForQueueFinish: (accessToken: string, queue: keyof QueuesResponseLegacyDto, ms?: number) => {
// eslint-disable-next-line no-async-promise-executor
return new Promise<void>(async (resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('Timed out waiting for queue to empty')), ms || 10_000);

View File

@@ -137,8 +137,10 @@ Class | Method | HTTP request | Description
*DeprecatedApi* | [**getAllUserAssetsByDeviceId**](doc//DeprecatedApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | Retrieve assets by device ID
*DeprecatedApi* | [**getDeltaSync**](doc//DeprecatedApi.md#getdeltasync) | **POST** /sync/delta-sync | Get delta sync for user
*DeprecatedApi* | [**getFullSyncForUser**](doc//DeprecatedApi.md#getfullsyncforuser) | **POST** /sync/full-sync | Get full sync for user
*DeprecatedApi* | [**getQueuesLegacy**](doc//DeprecatedApi.md#getqueueslegacy) | **GET** /jobs | Retrieve queue counts and status
*DeprecatedApi* | [**getRandom**](doc//DeprecatedApi.md#getrandom) | **GET** /assets/random | Get random assets
*DeprecatedApi* | [**replaceAsset**](doc//DeprecatedApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace asset
*DeprecatedApi* | [**runQueueCommandLegacy**](doc//DeprecatedApi.md#runqueuecommandlegacy) | **PUT** /jobs/{name} | Run jobs
*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive | Download asset archive
*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info | Retrieve download information
*DuplicatesApi* | [**deleteDuplicate**](doc//DuplicatesApi.md#deleteduplicate) | **DELETE** /duplicates/{id} | Delete a duplicate
@@ -198,6 +200,11 @@ Class | Method | HTTP request | Description
*PeopleApi* | [**updatePerson**](doc//PeopleApi.md#updateperson) | **PUT** /people/{id} | Update person
*PluginsApi* | [**getPlugin**](doc//PluginsApi.md#getplugin) | **GET** /plugins/{id} | Retrieve a plugin
*PluginsApi* | [**getPlugins**](doc//PluginsApi.md#getplugins) | **GET** /plugins | List all plugins
*QueuesApi* | [**emptyQueue**](doc//QueuesApi.md#emptyqueue) | **DELETE** /queues/{name}/jobs | Empty a queue
*QueuesApi* | [**getQueue**](doc//QueuesApi.md#getqueue) | **GET** /queues/{name} | Retrieve a queue
*QueuesApi* | [**getQueueJobs**](doc//QueuesApi.md#getqueuejobs) | **GET** /queues/{name}/jobs | Retrieve queue jobs
*QueuesApi* | [**getQueues**](doc//QueuesApi.md#getqueues) | **GET** /queues | List all queues
*QueuesApi* | [**updateQueue**](doc//QueuesApi.md#updatequeue) | **PUT** /queues/{name} | Update a queue
*SearchApi* | [**getAssetsByCity**](doc//SearchApi.md#getassetsbycity) | **GET** /search/cities | Retrieve assets by city
*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore | Retrieve explore data
*SearchApi* | [**getSearchSuggestions**](doc//SearchApi.md#getsearchsuggestions) | **GET** /search/suggestions | Retrieve search suggestions
@@ -396,6 +403,7 @@ Class | Method | HTTP request | Description
- [FoldersUpdate](doc//FoldersUpdate.md)
- [ImageFormat](doc//ImageFormat.md)
- [JobCreateDto](doc//JobCreateDto.md)
- [JobName](doc//JobName.md)
- [JobSettingsDto](doc//JobSettingsDto.md)
- [LibraryResponseDto](doc//LibraryResponseDto.md)
- [LibraryStatsResponseDto](doc//LibraryStatsResponseDto.md)
@@ -465,11 +473,16 @@ Class | Method | HTTP request | Description
- [PurchaseUpdate](doc//PurchaseUpdate.md)
- [QueueCommand](doc//QueueCommand.md)
- [QueueCommandDto](doc//QueueCommandDto.md)
- [QueueDeleteDto](doc//QueueDeleteDto.md)
- [QueueJobResponseDto](doc//QueueJobResponseDto.md)
- [QueueJobStatus](doc//QueueJobStatus.md)
- [QueueName](doc//QueueName.md)
- [QueueResponseDto](doc//QueueResponseDto.md)
- [QueueResponseLegacyDto](doc//QueueResponseLegacyDto.md)
- [QueueStatisticsDto](doc//QueueStatisticsDto.md)
- [QueueStatusDto](doc//QueueStatusDto.md)
- [QueuesResponseDto](doc//QueuesResponseDto.md)
- [QueueStatusLegacyDto](doc//QueueStatusLegacyDto.md)
- [QueueUpdateDto](doc//QueueUpdateDto.md)
- [QueuesResponseLegacyDto](doc//QueuesResponseLegacyDto.md)
- [RandomSearchDto](doc//RandomSearchDto.md)
- [RatingsResponse](doc//RatingsResponse.md)
- [RatingsUpdate](doc//RatingsUpdate.md)

View File

@@ -50,6 +50,7 @@ part 'api/notifications_admin_api.dart';
part 'api/partners_api.dart';
part 'api/people_api.dart';
part 'api/plugins_api.dart';
part 'api/queues_api.dart';
part 'api/search_api.dart';
part 'api/server_api.dart';
part 'api/sessions_api.dart';
@@ -154,6 +155,7 @@ part 'model/folders_response.dart';
part 'model/folders_update.dart';
part 'model/image_format.dart';
part 'model/job_create_dto.dart';
part 'model/job_name.dart';
part 'model/job_settings_dto.dart';
part 'model/library_response_dto.dart';
part 'model/library_stats_response_dto.dart';
@@ -223,11 +225,16 @@ part 'model/purchase_response.dart';
part 'model/purchase_update.dart';
part 'model/queue_command.dart';
part 'model/queue_command_dto.dart';
part 'model/queue_delete_dto.dart';
part 'model/queue_job_response_dto.dart';
part 'model/queue_job_status.dart';
part 'model/queue_name.dart';
part 'model/queue_response_dto.dart';
part 'model/queue_response_legacy_dto.dart';
part 'model/queue_statistics_dto.dart';
part 'model/queue_status_dto.dart';
part 'model/queues_response_dto.dart';
part 'model/queue_status_legacy_dto.dart';
part 'model/queue_update_dto.dart';
part 'model/queues_response_legacy_dto.dart';
part 'model/random_search_dto.dart';
part 'model/ratings_response.dart';
part 'model/ratings_update.dart';

View File

@@ -248,6 +248,54 @@ class DeprecatedApi {
return null;
}
/// Retrieve queue counts and status
///
/// Retrieve the counts of the current queue, as well as the current status.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getQueuesLegacyWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = 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(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Retrieve queue counts and status
///
/// Retrieve the counts of the current queue, as well as the current status.
Future<QueuesResponseLegacyDto?> getQueuesLegacy() async {
final response = await getQueuesLegacyWithHttpInfo();
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), 'QueuesResponseLegacyDto',) as QueuesResponseLegacyDto;
}
return null;
}
/// Get random assets
///
/// Retrieve a specified number of random assets for the authenticated user.
@@ -444,4 +492,65 @@ class DeprecatedApi {
}
return null;
}
/// Run jobs
///
/// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueCommandDto] queueCommandDto (required):
Future<Response> runQueueCommandLegacyWithHttpInfo(QueueName name, QueueCommandDto queueCommandDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/jobs/{name}'
.replaceAll('{name}', name.toString());
// ignore: prefer_final_locals
Object? postBody = queueCommandDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
apiPath,
'PUT',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Run jobs
///
/// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueCommandDto] queueCommandDto (required):
Future<QueueResponseLegacyDto?> runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async {
final response = await runQueueCommandLegacyWithHttpInfo(name, queueCommandDto,);
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), 'QueueResponseLegacyDto',) as QueueResponseLegacyDto;
}
return null;
}
}

View File

@@ -97,7 +97,7 @@ class JobsApi {
/// Retrieve queue counts and status
///
/// Retrieve the counts of the current queue, as well as the current status.
Future<QueuesResponseDto?> getQueuesLegacy() async {
Future<QueuesResponseLegacyDto?> getQueuesLegacy() async {
final response = await getQueuesLegacyWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
@@ -106,7 +106,7 @@ class JobsApi {
// 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), 'QueuesResponseDto',) as QueuesResponseDto;
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueuesResponseLegacyDto',) as QueuesResponseLegacyDto;
}
return null;
@@ -158,7 +158,7 @@ class JobsApi {
/// * [QueueName] name (required):
///
/// * [QueueCommandDto] queueCommandDto (required):
Future<QueueResponseDto?> runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async {
Future<QueueResponseLegacyDto?> runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async {
final response = await runQueueCommandLegacyWithHttpInfo(name, queueCommandDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
@@ -167,7 +167,7 @@ class JobsApi {
// 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), 'QueueResponseDto',) as QueueResponseDto;
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueueResponseLegacyDto',) as QueueResponseLegacyDto;
}
return null;

308
mobile/openapi/lib/api/queues_api.dart generated Normal file
View File

@@ -0,0 +1,308 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class QueuesApi {
QueuesApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
final ApiClient apiClient;
/// Empty a queue
///
/// Removes all jobs from the specified queue.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueDeleteDto] queueDeleteDto (required):
Future<Response> emptyQueueWithHttpInfo(QueueName name, QueueDeleteDto queueDeleteDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/queues/{name}/jobs'
.replaceAll('{name}', name.toString());
// ignore: prefer_final_locals
Object? postBody = queueDeleteDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
apiPath,
'DELETE',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Empty a queue
///
/// Removes all jobs from the specified queue.
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueDeleteDto] queueDeleteDto (required):
Future<void> emptyQueue(QueueName name, QueueDeleteDto queueDeleteDto,) async {
final response = await emptyQueueWithHttpInfo(name, queueDeleteDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Retrieve a queue
///
/// Retrieves a specific queue by its name.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [QueueName] name (required):
Future<Response> getQueueWithHttpInfo(QueueName name,) async {
// ignore: prefer_const_declarations
final apiPath = r'/queues/{name}'
.replaceAll('{name}', name.toString());
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Retrieve a queue
///
/// Retrieves a specific queue by its name.
///
/// Parameters:
///
/// * [QueueName] name (required):
Future<QueueResponseDto?> getQueue(QueueName name,) async {
final response = await getQueueWithHttpInfo(name,);
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), 'QueueResponseDto',) as QueueResponseDto;
}
return null;
}
/// Retrieve queue jobs
///
/// Retrieves a list of queue jobs from the specified queue.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [List<QueueJobStatus>] status:
Future<Response> getQueueJobsWithHttpInfo(QueueName name, { List<QueueJobStatus>? status, }) async {
// ignore: prefer_const_declarations
final apiPath = r'/queues/{name}/jobs'
.replaceAll('{name}', name.toString());
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (status != null) {
queryParams.addAll(_queryParams('multi', 'status', status));
}
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Retrieve queue jobs
///
/// Retrieves a list of queue jobs from the specified queue.
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [List<QueueJobStatus>] status:
Future<List<QueueJobResponseDto>?> getQueueJobs(QueueName name, { List<QueueJobStatus>? status, }) async {
final response = await getQueueJobsWithHttpInfo(name, status: status, );
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) {
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, 'List<QueueJobResponseDto>') as List)
.cast<QueueJobResponseDto>()
.toList(growable: false);
}
return null;
}
/// List all queues
///
/// Retrieves a list of queues.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getQueuesWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/queues';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// List all queues
///
/// Retrieves a list of queues.
Future<List<QueueResponseDto>?> getQueues() async {
final response = await getQueuesWithHttpInfo();
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) {
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, 'List<QueueResponseDto>') as List)
.cast<QueueResponseDto>()
.toList(growable: false);
}
return null;
}
/// Update a queue
///
/// Change the paused status of a specific queue.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueUpdateDto] queueUpdateDto (required):
Future<Response> updateQueueWithHttpInfo(QueueName name, QueueUpdateDto queueUpdateDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/queues/{name}'
.replaceAll('{name}', name.toString());
// ignore: prefer_final_locals
Object? postBody = queueUpdateDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
apiPath,
'PUT',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Update a queue
///
/// Change the paused status of a specific queue.
///
/// Parameters:
///
/// * [QueueName] name (required):
///
/// * [QueueUpdateDto] queueUpdateDto (required):
Future<QueueResponseDto?> updateQueue(QueueName name, QueueUpdateDto queueUpdateDto,) async {
final response = await updateQueueWithHttpInfo(name, queueUpdateDto,);
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), 'QueueResponseDto',) as QueueResponseDto;
}
return null;
}
}

View File

@@ -358,6 +358,8 @@ class ApiClient {
return ImageFormatTypeTransformer().decode(value);
case 'JobCreateDto':
return JobCreateDto.fromJson(value);
case 'JobName':
return JobNameTypeTransformer().decode(value);
case 'JobSettingsDto':
return JobSettingsDto.fromJson(value);
case 'LibraryResponseDto':
@@ -496,16 +498,26 @@ class ApiClient {
return QueueCommandTypeTransformer().decode(value);
case 'QueueCommandDto':
return QueueCommandDto.fromJson(value);
case 'QueueDeleteDto':
return QueueDeleteDto.fromJson(value);
case 'QueueJobResponseDto':
return QueueJobResponseDto.fromJson(value);
case 'QueueJobStatus':
return QueueJobStatusTypeTransformer().decode(value);
case 'QueueName':
return QueueNameTypeTransformer().decode(value);
case 'QueueResponseDto':
return QueueResponseDto.fromJson(value);
case 'QueueResponseLegacyDto':
return QueueResponseLegacyDto.fromJson(value);
case 'QueueStatisticsDto':
return QueueStatisticsDto.fromJson(value);
case 'QueueStatusDto':
return QueueStatusDto.fromJson(value);
case 'QueuesResponseDto':
return QueuesResponseDto.fromJson(value);
case 'QueueStatusLegacyDto':
return QueueStatusLegacyDto.fromJson(value);
case 'QueueUpdateDto':
return QueueUpdateDto.fromJson(value);
case 'QueuesResponseLegacyDto':
return QueuesResponseLegacyDto.fromJson(value);
case 'RandomSearchDto':
return RandomSearchDto.fromJson(value);
case 'RatingsResponse':

View File

@@ -94,6 +94,9 @@ String parameterToString(dynamic value) {
if (value is ImageFormat) {
return ImageFormatTypeTransformer().encode(value).toString();
}
if (value is JobName) {
return JobNameTypeTransformer().encode(value).toString();
}
if (value is LogLevel) {
return LogLevelTypeTransformer().encode(value).toString();
}
@@ -133,6 +136,9 @@ String parameterToString(dynamic value) {
if (value is QueueCommand) {
return QueueCommandTypeTransformer().encode(value).toString();
}
if (value is QueueJobStatus) {
return QueueJobStatusTypeTransformer().encode(value).toString();
}
if (value is QueueName) {
return QueueNameTypeTransformer().encode(value).toString();
}

244
mobile/openapi/lib/model/job_name.dart generated Normal file
View File

@@ -0,0 +1,244 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class JobName {
/// Instantiate a new enum with the provided [value].
const JobName._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const assetDelete = JobName._(r'AssetDelete');
static const assetDeleteCheck = JobName._(r'AssetDeleteCheck');
static const assetDetectFacesQueueAll = JobName._(r'AssetDetectFacesQueueAll');
static const assetDetectFaces = JobName._(r'AssetDetectFaces');
static const assetDetectDuplicatesQueueAll = JobName._(r'AssetDetectDuplicatesQueueAll');
static const assetDetectDuplicates = JobName._(r'AssetDetectDuplicates');
static const assetEncodeVideoQueueAll = JobName._(r'AssetEncodeVideoQueueAll');
static const assetEncodeVideo = JobName._(r'AssetEncodeVideo');
static const assetEmptyTrash = JobName._(r'AssetEmptyTrash');
static const assetExtractMetadataQueueAll = JobName._(r'AssetExtractMetadataQueueAll');
static const assetExtractMetadata = JobName._(r'AssetExtractMetadata');
static const assetFileMigration = JobName._(r'AssetFileMigration');
static const assetGenerateThumbnailsQueueAll = JobName._(r'AssetGenerateThumbnailsQueueAll');
static const assetGenerateThumbnails = JobName._(r'AssetGenerateThumbnails');
static const auditLogCleanup = JobName._(r'AuditLogCleanup');
static const auditTableCleanup = JobName._(r'AuditTableCleanup');
static const databaseBackup = JobName._(r'DatabaseBackup');
static const facialRecognitionQueueAll = JobName._(r'FacialRecognitionQueueAll');
static const facialRecognition = JobName._(r'FacialRecognition');
static const fileDelete = JobName._(r'FileDelete');
static const fileMigrationQueueAll = JobName._(r'FileMigrationQueueAll');
static const libraryDeleteCheck = JobName._(r'LibraryDeleteCheck');
static const libraryDelete = JobName._(r'LibraryDelete');
static const libraryRemoveAsset = JobName._(r'LibraryRemoveAsset');
static const libraryScanAssetsQueueAll = JobName._(r'LibraryScanAssetsQueueAll');
static const librarySyncAssets = JobName._(r'LibrarySyncAssets');
static const librarySyncFilesQueueAll = JobName._(r'LibrarySyncFilesQueueAll');
static const librarySyncFiles = JobName._(r'LibrarySyncFiles');
static const libraryScanQueueAll = JobName._(r'LibraryScanQueueAll');
static const memoryCleanup = JobName._(r'MemoryCleanup');
static const memoryGenerate = JobName._(r'MemoryGenerate');
static const notificationsCleanup = JobName._(r'NotificationsCleanup');
static const notifyUserSignup = JobName._(r'NotifyUserSignup');
static const notifyAlbumInvite = JobName._(r'NotifyAlbumInvite');
static const notifyAlbumUpdate = JobName._(r'NotifyAlbumUpdate');
static const userDelete = JobName._(r'UserDelete');
static const userDeleteCheck = JobName._(r'UserDeleteCheck');
static const userSyncUsage = JobName._(r'UserSyncUsage');
static const personCleanup = JobName._(r'PersonCleanup');
static const personFileMigration = JobName._(r'PersonFileMigration');
static const personGenerateThumbnail = JobName._(r'PersonGenerateThumbnail');
static const sessionCleanup = JobName._(r'SessionCleanup');
static const sendMail = JobName._(r'SendMail');
static const sidecarQueueAll = JobName._(r'SidecarQueueAll');
static const sidecarCheck = JobName._(r'SidecarCheck');
static const sidecarWrite = JobName._(r'SidecarWrite');
static const smartSearchQueueAll = JobName._(r'SmartSearchQueueAll');
static const smartSearch = JobName._(r'SmartSearch');
static const storageTemplateMigration = JobName._(r'StorageTemplateMigration');
static const storageTemplateMigrationSingle = JobName._(r'StorageTemplateMigrationSingle');
static const tagCleanup = JobName._(r'TagCleanup');
static const versionCheck = JobName._(r'VersionCheck');
static const ocrQueueAll = JobName._(r'OcrQueueAll');
static const ocr = JobName._(r'Ocr');
static const workflowRun = JobName._(r'WorkflowRun');
/// List of all possible values in this [enum][JobName].
static const values = <JobName>[
assetDelete,
assetDeleteCheck,
assetDetectFacesQueueAll,
assetDetectFaces,
assetDetectDuplicatesQueueAll,
assetDetectDuplicates,
assetEncodeVideoQueueAll,
assetEncodeVideo,
assetEmptyTrash,
assetExtractMetadataQueueAll,
assetExtractMetadata,
assetFileMigration,
assetGenerateThumbnailsQueueAll,
assetGenerateThumbnails,
auditLogCleanup,
auditTableCleanup,
databaseBackup,
facialRecognitionQueueAll,
facialRecognition,
fileDelete,
fileMigrationQueueAll,
libraryDeleteCheck,
libraryDelete,
libraryRemoveAsset,
libraryScanAssetsQueueAll,
librarySyncAssets,
librarySyncFilesQueueAll,
librarySyncFiles,
libraryScanQueueAll,
memoryCleanup,
memoryGenerate,
notificationsCleanup,
notifyUserSignup,
notifyAlbumInvite,
notifyAlbumUpdate,
userDelete,
userDeleteCheck,
userSyncUsage,
personCleanup,
personFileMigration,
personGenerateThumbnail,
sessionCleanup,
sendMail,
sidecarQueueAll,
sidecarCheck,
sidecarWrite,
smartSearchQueueAll,
smartSearch,
storageTemplateMigration,
storageTemplateMigrationSingle,
tagCleanup,
versionCheck,
ocrQueueAll,
ocr,
workflowRun,
];
static JobName? fromJson(dynamic value) => JobNameTypeTransformer().decode(value);
static List<JobName> listFromJson(dynamic json, {bool growable = false,}) {
final result = <JobName>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = JobName.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [JobName] to String,
/// and [decode] dynamic data back to [JobName].
class JobNameTypeTransformer {
factory JobNameTypeTransformer() => _instance ??= const JobNameTypeTransformer._();
const JobNameTypeTransformer._();
String encode(JobName data) => data.value;
/// Decodes a [dynamic value][data] to a JobName.
///
/// 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.
JobName? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'AssetDelete': return JobName.assetDelete;
case r'AssetDeleteCheck': return JobName.assetDeleteCheck;
case r'AssetDetectFacesQueueAll': return JobName.assetDetectFacesQueueAll;
case r'AssetDetectFaces': return JobName.assetDetectFaces;
case r'AssetDetectDuplicatesQueueAll': return JobName.assetDetectDuplicatesQueueAll;
case r'AssetDetectDuplicates': return JobName.assetDetectDuplicates;
case r'AssetEncodeVideoQueueAll': return JobName.assetEncodeVideoQueueAll;
case r'AssetEncodeVideo': return JobName.assetEncodeVideo;
case r'AssetEmptyTrash': return JobName.assetEmptyTrash;
case r'AssetExtractMetadataQueueAll': return JobName.assetExtractMetadataQueueAll;
case r'AssetExtractMetadata': return JobName.assetExtractMetadata;
case r'AssetFileMigration': return JobName.assetFileMigration;
case r'AssetGenerateThumbnailsQueueAll': return JobName.assetGenerateThumbnailsQueueAll;
case r'AssetGenerateThumbnails': return JobName.assetGenerateThumbnails;
case r'AuditLogCleanup': return JobName.auditLogCleanup;
case r'AuditTableCleanup': return JobName.auditTableCleanup;
case r'DatabaseBackup': return JobName.databaseBackup;
case r'FacialRecognitionQueueAll': return JobName.facialRecognitionQueueAll;
case r'FacialRecognition': return JobName.facialRecognition;
case r'FileDelete': return JobName.fileDelete;
case r'FileMigrationQueueAll': return JobName.fileMigrationQueueAll;
case r'LibraryDeleteCheck': return JobName.libraryDeleteCheck;
case r'LibraryDelete': return JobName.libraryDelete;
case r'LibraryRemoveAsset': return JobName.libraryRemoveAsset;
case r'LibraryScanAssetsQueueAll': return JobName.libraryScanAssetsQueueAll;
case r'LibrarySyncAssets': return JobName.librarySyncAssets;
case r'LibrarySyncFilesQueueAll': return JobName.librarySyncFilesQueueAll;
case r'LibrarySyncFiles': return JobName.librarySyncFiles;
case r'LibraryScanQueueAll': return JobName.libraryScanQueueAll;
case r'MemoryCleanup': return JobName.memoryCleanup;
case r'MemoryGenerate': return JobName.memoryGenerate;
case r'NotificationsCleanup': return JobName.notificationsCleanup;
case r'NotifyUserSignup': return JobName.notifyUserSignup;
case r'NotifyAlbumInvite': return JobName.notifyAlbumInvite;
case r'NotifyAlbumUpdate': return JobName.notifyAlbumUpdate;
case r'UserDelete': return JobName.userDelete;
case r'UserDeleteCheck': return JobName.userDeleteCheck;
case r'UserSyncUsage': return JobName.userSyncUsage;
case r'PersonCleanup': return JobName.personCleanup;
case r'PersonFileMigration': return JobName.personFileMigration;
case r'PersonGenerateThumbnail': return JobName.personGenerateThumbnail;
case r'SessionCleanup': return JobName.sessionCleanup;
case r'SendMail': return JobName.sendMail;
case r'SidecarQueueAll': return JobName.sidecarQueueAll;
case r'SidecarCheck': return JobName.sidecarCheck;
case r'SidecarWrite': return JobName.sidecarWrite;
case r'SmartSearchQueueAll': return JobName.smartSearchQueueAll;
case r'SmartSearch': return JobName.smartSearch;
case r'StorageTemplateMigration': return JobName.storageTemplateMigration;
case r'StorageTemplateMigrationSingle': return JobName.storageTemplateMigrationSingle;
case r'TagCleanup': return JobName.tagCleanup;
case r'VersionCheck': return JobName.versionCheck;
case r'OcrQueueAll': return JobName.ocrQueueAll;
case r'Ocr': return JobName.ocr;
case r'WorkflowRun': return JobName.workflowRun;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [JobNameTypeTransformer] instance.
static JobNameTypeTransformer? _instance;
}

View File

@@ -152,6 +152,12 @@ class Permission {
static const userProfileImagePeriodRead = Permission._(r'userProfileImage.read');
static const userProfileImagePeriodUpdate = Permission._(r'userProfileImage.update');
static const userProfileImagePeriodDelete = Permission._(r'userProfileImage.delete');
static const queuePeriodRead = Permission._(r'queue.read');
static const queuePeriodUpdate = Permission._(r'queue.update');
static const queueJobPeriodCreate = Permission._(r'queueJob.create');
static const queueJobPeriodRead = Permission._(r'queueJob.read');
static const queueJobPeriodUpdate = Permission._(r'queueJob.update');
static const queueJobPeriodDelete = Permission._(r'queueJob.delete');
static const workflowPeriodCreate = Permission._(r'workflow.create');
static const workflowPeriodRead = Permission._(r'workflow.read');
static const workflowPeriodUpdate = Permission._(r'workflow.update');
@@ -294,6 +300,12 @@ class Permission {
userProfileImagePeriodRead,
userProfileImagePeriodUpdate,
userProfileImagePeriodDelete,
queuePeriodRead,
queuePeriodUpdate,
queueJobPeriodCreate,
queueJobPeriodRead,
queueJobPeriodUpdate,
queueJobPeriodDelete,
workflowPeriodCreate,
workflowPeriodRead,
workflowPeriodUpdate,
@@ -471,6 +483,12 @@ class PermissionTypeTransformer {
case r'userProfileImage.read': return Permission.userProfileImagePeriodRead;
case r'userProfileImage.update': return Permission.userProfileImagePeriodUpdate;
case r'userProfileImage.delete': return Permission.userProfileImagePeriodDelete;
case r'queue.read': return Permission.queuePeriodRead;
case r'queue.update': return Permission.queuePeriodUpdate;
case r'queueJob.create': return Permission.queueJobPeriodCreate;
case r'queueJob.read': return Permission.queueJobPeriodRead;
case r'queueJob.update': return Permission.queueJobPeriodUpdate;
case r'queueJob.delete': return Permission.queueJobPeriodDelete;
case r'workflow.create': return Permission.workflowPeriodCreate;
case r'workflow.read': return Permission.workflowPeriodRead;
case r'workflow.update': return Permission.workflowPeriodUpdate;

View File

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

View File

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

View File

@@ -0,0 +1,97 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class QueueJobStatus {
/// Instantiate a new enum with the provided [value].
const QueueJobStatus._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const active = QueueJobStatus._(r'active');
static const failed = QueueJobStatus._(r'failed');
static const completed = QueueJobStatus._(r'completed');
static const delayed = QueueJobStatus._(r'delayed');
static const waiting = QueueJobStatus._(r'waiting');
static const paused = QueueJobStatus._(r'paused');
/// List of all possible values in this [enum][QueueJobStatus].
static const values = <QueueJobStatus>[
active,
failed,
completed,
delayed,
waiting,
paused,
];
static QueueJobStatus? fromJson(dynamic value) => QueueJobStatusTypeTransformer().decode(value);
static List<QueueJobStatus> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueJobStatus>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueueJobStatus.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [QueueJobStatus] to String,
/// and [decode] dynamic data back to [QueueJobStatus].
class QueueJobStatusTypeTransformer {
factory QueueJobStatusTypeTransformer() => _instance ??= const QueueJobStatusTypeTransformer._();
const QueueJobStatusTypeTransformer._();
String encode(QueueJobStatus data) => data.value;
/// Decodes a [dynamic value][data] to a QueueJobStatus.
///
/// 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.
QueueJobStatus? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'active': return QueueJobStatus.active;
case r'failed': return QueueJobStatus.failed;
case r'completed': return QueueJobStatus.completed;
case r'delayed': return QueueJobStatus.delayed;
case r'waiting': return QueueJobStatus.waiting;
case r'paused': return QueueJobStatus.paused;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [QueueJobStatusTypeTransformer] instance.
static QueueJobStatusTypeTransformer? _instance;
}

View File

@@ -13,32 +13,38 @@ part of openapi.api;
class QueueResponseDto {
/// Returns a new [QueueResponseDto] instance.
QueueResponseDto({
required this.jobCounts,
required this.queueStatus,
required this.isPaused,
required this.name,
required this.statistics,
});
QueueStatisticsDto jobCounts;
bool isPaused;
QueueStatusDto queueStatus;
QueueName name;
QueueStatisticsDto statistics;
@override
bool operator ==(Object other) => identical(this, other) || other is QueueResponseDto &&
other.jobCounts == jobCounts &&
other.queueStatus == queueStatus;
other.isPaused == isPaused &&
other.name == name &&
other.statistics == statistics;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(jobCounts.hashCode) +
(queueStatus.hashCode);
(isPaused.hashCode) +
(name.hashCode) +
(statistics.hashCode);
@override
String toString() => 'QueueResponseDto[jobCounts=$jobCounts, queueStatus=$queueStatus]';
String toString() => 'QueueResponseDto[isPaused=$isPaused, name=$name, statistics=$statistics]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'jobCounts'] = this.jobCounts;
json[r'queueStatus'] = this.queueStatus;
json[r'isPaused'] = this.isPaused;
json[r'name'] = this.name;
json[r'statistics'] = this.statistics;
return json;
}
@@ -51,8 +57,9 @@ class QueueResponseDto {
final json = value.cast<String, dynamic>();
return QueueResponseDto(
jobCounts: QueueStatisticsDto.fromJson(json[r'jobCounts'])!,
queueStatus: QueueStatusDto.fromJson(json[r'queueStatus'])!,
isPaused: mapValueOfType<bool>(json, r'isPaused')!,
name: QueueName.fromJson(json[r'name'])!,
statistics: QueueStatisticsDto.fromJson(json[r'statistics'])!,
);
}
return null;
@@ -100,8 +107,9 @@ class QueueResponseDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'jobCounts',
'queueStatus',
'isPaused',
'name',
'statistics',
};
}

View File

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

View File

@@ -10,9 +10,9 @@
part of openapi.api;
class QueueStatusDto {
/// Returns a new [QueueStatusDto] instance.
QueueStatusDto({
class QueueStatusLegacyDto {
/// Returns a new [QueueStatusLegacyDto] instance.
QueueStatusLegacyDto({
required this.isActive,
required this.isPaused,
});
@@ -22,7 +22,7 @@ class QueueStatusDto {
bool isPaused;
@override
bool operator ==(Object other) => identical(this, other) || other is QueueStatusDto &&
bool operator ==(Object other) => identical(this, other) || other is QueueStatusLegacyDto &&
other.isActive == isActive &&
other.isPaused == isPaused;
@@ -33,7 +33,7 @@ class QueueStatusDto {
(isPaused.hashCode);
@override
String toString() => 'QueueStatusDto[isActive=$isActive, isPaused=$isPaused]';
String toString() => 'QueueStatusLegacyDto[isActive=$isActive, isPaused=$isPaused]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -42,15 +42,15 @@ class QueueStatusDto {
return json;
}
/// Returns a new [QueueStatusDto] instance and imports its values from
/// Returns a new [QueueStatusLegacyDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static QueueStatusDto? fromJson(dynamic value) {
upgradeDto(value, "QueueStatusDto");
static QueueStatusLegacyDto? fromJson(dynamic value) {
upgradeDto(value, "QueueStatusLegacyDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return QueueStatusDto(
return QueueStatusLegacyDto(
isActive: mapValueOfType<bool>(json, r'isActive')!,
isPaused: mapValueOfType<bool>(json, r'isPaused')!,
);
@@ -58,11 +58,11 @@ class QueueStatusDto {
return null;
}
static List<QueueStatusDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueStatusDto>[];
static List<QueueStatusLegacyDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueueStatusLegacyDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueueStatusDto.fromJson(row);
final value = QueueStatusLegacyDto.fromJson(row);
if (value != null) {
result.add(value);
}
@@ -71,12 +71,12 @@ class QueueStatusDto {
return result.toList(growable: growable);
}
static Map<String, QueueStatusDto> mapFromJson(dynamic json) {
final map = <String, QueueStatusDto>{};
static Map<String, QueueStatusLegacyDto> mapFromJson(dynamic json) {
final map = <String, QueueStatusLegacyDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = QueueStatusDto.fromJson(entry.value);
final value = QueueStatusLegacyDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
@@ -85,14 +85,14 @@ class QueueStatusDto {
return map;
}
// maps a json object with a list of QueueStatusDto-objects as value to a dart map
static Map<String, List<QueueStatusDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueueStatusDto>>{};
// maps a json object with a list of QueueStatusLegacyDto-objects as value to a dart map
static Map<String, List<QueueStatusLegacyDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueueStatusLegacyDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = QueueStatusDto.listFromJson(entry.value, growable: growable,);
map[entry.key] = QueueStatusLegacyDto.listFromJson(entry.value, growable: growable,);
}
}
return map;

View File

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

View File

@@ -10,9 +10,9 @@
part of openapi.api;
class QueuesResponseDto {
/// Returns a new [QueuesResponseDto] instance.
QueuesResponseDto({
class QueuesResponseLegacyDto {
/// Returns a new [QueuesResponseLegacyDto] instance.
QueuesResponseLegacyDto({
required this.backgroundTask,
required this.backupDatabase,
required this.duplicateDetection,
@@ -32,42 +32,42 @@ class QueuesResponseDto {
required this.workflow,
});
QueueResponseDto backgroundTask;
QueueResponseLegacyDto backgroundTask;
QueueResponseDto backupDatabase;
QueueResponseLegacyDto backupDatabase;
QueueResponseDto duplicateDetection;
QueueResponseLegacyDto duplicateDetection;
QueueResponseDto faceDetection;
QueueResponseLegacyDto faceDetection;
QueueResponseDto facialRecognition;
QueueResponseLegacyDto facialRecognition;
QueueResponseDto library_;
QueueResponseLegacyDto library_;
QueueResponseDto metadataExtraction;
QueueResponseLegacyDto metadataExtraction;
QueueResponseDto migration;
QueueResponseLegacyDto migration;
QueueResponseDto notifications;
QueueResponseLegacyDto notifications;
QueueResponseDto ocr;
QueueResponseLegacyDto ocr;
QueueResponseDto search;
QueueResponseLegacyDto search;
QueueResponseDto sidecar;
QueueResponseLegacyDto sidecar;
QueueResponseDto smartSearch;
QueueResponseLegacyDto smartSearch;
QueueResponseDto storageTemplateMigration;
QueueResponseLegacyDto storageTemplateMigration;
QueueResponseDto thumbnailGeneration;
QueueResponseLegacyDto thumbnailGeneration;
QueueResponseDto videoConversion;
QueueResponseLegacyDto videoConversion;
QueueResponseDto workflow;
QueueResponseLegacyDto workflow;
@override
bool operator ==(Object other) => identical(this, other) || other is QueuesResponseDto &&
bool operator ==(Object other) => identical(this, other) || other is QueuesResponseLegacyDto &&
other.backgroundTask == backgroundTask &&
other.backupDatabase == backupDatabase &&
other.duplicateDetection == duplicateDetection &&
@@ -108,7 +108,7 @@ class QueuesResponseDto {
(workflow.hashCode);
@override
String toString() => 'QueuesResponseDto[backgroundTask=$backgroundTask, backupDatabase=$backupDatabase, duplicateDetection=$duplicateDetection, faceDetection=$faceDetection, facialRecognition=$facialRecognition, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]';
String toString() => 'QueuesResponseLegacyDto[backgroundTask=$backgroundTask, backupDatabase=$backupDatabase, duplicateDetection=$duplicateDetection, faceDetection=$faceDetection, facialRecognition=$facialRecognition, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -132,42 +132,42 @@ class QueuesResponseDto {
return json;
}
/// Returns a new [QueuesResponseDto] instance and imports its values from
/// Returns a new [QueuesResponseLegacyDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static QueuesResponseDto? fromJson(dynamic value) {
upgradeDto(value, "QueuesResponseDto");
static QueuesResponseLegacyDto? fromJson(dynamic value) {
upgradeDto(value, "QueuesResponseLegacyDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return QueuesResponseDto(
backgroundTask: QueueResponseDto.fromJson(json[r'backgroundTask'])!,
backupDatabase: QueueResponseDto.fromJson(json[r'backupDatabase'])!,
duplicateDetection: QueueResponseDto.fromJson(json[r'duplicateDetection'])!,
faceDetection: QueueResponseDto.fromJson(json[r'faceDetection'])!,
facialRecognition: QueueResponseDto.fromJson(json[r'facialRecognition'])!,
library_: QueueResponseDto.fromJson(json[r'library'])!,
metadataExtraction: QueueResponseDto.fromJson(json[r'metadataExtraction'])!,
migration: QueueResponseDto.fromJson(json[r'migration'])!,
notifications: QueueResponseDto.fromJson(json[r'notifications'])!,
ocr: QueueResponseDto.fromJson(json[r'ocr'])!,
search: QueueResponseDto.fromJson(json[r'search'])!,
sidecar: QueueResponseDto.fromJson(json[r'sidecar'])!,
smartSearch: QueueResponseDto.fromJson(json[r'smartSearch'])!,
storageTemplateMigration: QueueResponseDto.fromJson(json[r'storageTemplateMigration'])!,
thumbnailGeneration: QueueResponseDto.fromJson(json[r'thumbnailGeneration'])!,
videoConversion: QueueResponseDto.fromJson(json[r'videoConversion'])!,
workflow: QueueResponseDto.fromJson(json[r'workflow'])!,
return QueuesResponseLegacyDto(
backgroundTask: QueueResponseLegacyDto.fromJson(json[r'backgroundTask'])!,
backupDatabase: QueueResponseLegacyDto.fromJson(json[r'backupDatabase'])!,
duplicateDetection: QueueResponseLegacyDto.fromJson(json[r'duplicateDetection'])!,
faceDetection: QueueResponseLegacyDto.fromJson(json[r'faceDetection'])!,
facialRecognition: QueueResponseLegacyDto.fromJson(json[r'facialRecognition'])!,
library_: QueueResponseLegacyDto.fromJson(json[r'library'])!,
metadataExtraction: QueueResponseLegacyDto.fromJson(json[r'metadataExtraction'])!,
migration: QueueResponseLegacyDto.fromJson(json[r'migration'])!,
notifications: QueueResponseLegacyDto.fromJson(json[r'notifications'])!,
ocr: QueueResponseLegacyDto.fromJson(json[r'ocr'])!,
search: QueueResponseLegacyDto.fromJson(json[r'search'])!,
sidecar: QueueResponseLegacyDto.fromJson(json[r'sidecar'])!,
smartSearch: QueueResponseLegacyDto.fromJson(json[r'smartSearch'])!,
storageTemplateMigration: QueueResponseLegacyDto.fromJson(json[r'storageTemplateMigration'])!,
thumbnailGeneration: QueueResponseLegacyDto.fromJson(json[r'thumbnailGeneration'])!,
videoConversion: QueueResponseLegacyDto.fromJson(json[r'videoConversion'])!,
workflow: QueueResponseLegacyDto.fromJson(json[r'workflow'])!,
);
}
return null;
}
static List<QueuesResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueuesResponseDto>[];
static List<QueuesResponseLegacyDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <QueuesResponseLegacyDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = QueuesResponseDto.fromJson(row);
final value = QueuesResponseLegacyDto.fromJson(row);
if (value != null) {
result.add(value);
}
@@ -176,12 +176,12 @@ class QueuesResponseDto {
return result.toList(growable: growable);
}
static Map<String, QueuesResponseDto> mapFromJson(dynamic json) {
final map = <String, QueuesResponseDto>{};
static Map<String, QueuesResponseLegacyDto> mapFromJson(dynamic json) {
final map = <String, QueuesResponseLegacyDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = QueuesResponseDto.fromJson(entry.value);
final value = QueuesResponseLegacyDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
@@ -190,14 +190,14 @@ class QueuesResponseDto {
return map;
}
// maps a json object with a list of QueuesResponseDto-objects as value to a dart map
static Map<String, List<QueuesResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueuesResponseDto>>{};
// maps a json object with a list of QueuesResponseLegacyDto-objects as value to a dart map
static Map<String, List<QueuesResponseLegacyDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<QueuesResponseLegacyDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = QueuesResponseDto.listFromJson(entry.value, growable: growable,);
map[entry.key] = QueuesResponseLegacyDto.listFromJson(entry.value, growable: growable,);
}
}
return map;

View File

@@ -4929,6 +4929,7 @@
},
"/jobs": {
"get": {
"deprecated": true,
"description": "Retrieve the counts of the current queue, as well as the current status.",
"operationId": "getQueuesLegacy",
"parameters": [],
@@ -4937,7 +4938,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/QueuesResponseDto"
"$ref": "#/components/schemas/QueuesResponseLegacyDto"
}
}
},
@@ -4957,7 +4958,8 @@
],
"summary": "Retrieve queue counts and status",
"tags": [
"Jobs"
"Jobs",
"Deprecated"
],
"x-immich-admin-only": true,
"x-immich-history": [
@@ -4972,10 +4974,14 @@
{
"version": "v2",
"state": "Stable"
},
{
"version": "v2.4.0",
"state": "Deprecated"
}
],
"x-immich-permission": "job.read",
"x-immich-state": "Stable"
"x-immich-state": "Deprecated"
},
"post": {
"description": "Run a specific job. Most jobs are queued automatically, but this endpoint allows for manual creation of a handful of jobs, including various cleanup tasks, as well as creating a new database backup.",
@@ -5032,6 +5038,7 @@
},
"/jobs/{name}": {
"put": {
"deprecated": true,
"description": "Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.",
"operationId": "runQueueCommandLegacy",
"parameters": [
@@ -5059,7 +5066,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
}
}
},
@@ -5079,7 +5086,8 @@
],
"summary": "Run jobs",
"tags": [
"Jobs"
"Jobs",
"Deprecated"
],
"x-immich-admin-only": true,
"x-immich-history": [
@@ -5094,10 +5102,14 @@
{
"version": "v2",
"state": "Stable"
},
{
"version": "v2.4.0",
"state": "Deprecated"
}
],
"x-immich-permission": "job.create",
"x-immich-state": "Stable"
"x-immich-state": "Deprecated"
}
},
"/libraries": {
@@ -8064,6 +8076,303 @@
"x-immich-state": "Alpha"
}
},
"/queues": {
"get": {
"description": "Retrieves a list of queues.",
"operationId": "getQueues",
"parameters": [],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/QueueResponseDto"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"summary": "List all queues",
"tags": [
"Queues"
],
"x-immich-admin-only": true,
"x-immich-history": [
{
"version": "v2.4.0",
"state": "Added"
},
{
"version": "v2.4.0",
"state": "Alpha"
}
],
"x-immich-permission": "queue.read",
"x-immich-state": "Alpha"
}
},
"/queues/{name}": {
"get": {
"description": "Retrieves a specific queue by its name.",
"operationId": "getQueue",
"parameters": [
{
"name": "name",
"required": true,
"in": "path",
"schema": {
"$ref": "#/components/schemas/QueueName"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/QueueResponseDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"summary": "Retrieve a queue",
"tags": [
"Queues"
],
"x-immich-admin-only": true,
"x-immich-history": [
{
"version": "v2.4.0",
"state": "Added"
},
{
"version": "v2.4.0",
"state": "Alpha"
}
],
"x-immich-permission": "queue.read",
"x-immich-state": "Alpha"
},
"put": {
"description": "Change the paused status of a specific queue.",
"operationId": "updateQueue",
"parameters": [
{
"name": "name",
"required": true,
"in": "path",
"schema": {
"$ref": "#/components/schemas/QueueName"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/QueueUpdateDto"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/QueueResponseDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"summary": "Update a queue",
"tags": [
"Queues"
],
"x-immich-admin-only": true,
"x-immich-history": [
{
"version": "v2.4.0",
"state": "Added"
},
{
"version": "v2.4.0",
"state": "Alpha"
}
],
"x-immich-permission": "queue.update",
"x-immich-state": "Alpha"
}
},
"/queues/{name}/jobs": {
"delete": {
"description": "Removes all jobs from the specified queue.",
"operationId": "emptyQueue",
"parameters": [
{
"name": "name",
"required": true,
"in": "path",
"schema": {
"$ref": "#/components/schemas/QueueName"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/QueueDeleteDto"
}
}
},
"required": true
},
"responses": {
"204": {
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"summary": "Empty a queue",
"tags": [
"Queues"
],
"x-immich-admin-only": true,
"x-immich-history": [
{
"version": "v2.4.0",
"state": "Added"
},
{
"version": "v2.4.0",
"state": "Alpha"
}
],
"x-immich-permission": "queueJob.delete",
"x-immich-state": "Alpha"
},
"get": {
"description": "Retrieves a list of queue jobs from the specified queue.",
"operationId": "getQueueJobs",
"parameters": [
{
"name": "name",
"required": true,
"in": "path",
"schema": {
"$ref": "#/components/schemas/QueueName"
}
},
{
"name": "status",
"required": false,
"in": "query",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/QueueJobStatus"
}
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/QueueJobResponseDto"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"summary": "Retrieve queue jobs",
"tags": [
"Queues"
],
"x-immich-admin-only": true,
"x-immich-history": [
{
"version": "v2.4.0",
"state": "Added"
},
{
"version": "v2.4.0",
"state": "Alpha"
}
],
"x-immich-permission": "queueJob.read",
"x-immich-state": "Alpha"
}
},
"/search/cities": {
"get": {
"description": "Retrieve a list of assets with each asset belonging to a different city. This endpoint is used on the places pages to show a single thumbnail for each city the user has assets in.",
@@ -14043,6 +14352,10 @@
"name": "Plugins",
"description": "A plugin is an installed module that makes filters and actions available for the workflow feature."
},
{
"name": "Queues",
"description": "Queues and background jobs are used for processing tasks asynchronously. Queues can be paused and resumed as needed."
},
{
"name": "Search",
"description": "Endpoints related to searching assets via text, smart search, optical character recognition (OCR), and other filters like person, album, and other metadata. Search endpoints usually support pagination and sorting."
@@ -16291,6 +16604,66 @@
],
"type": "object"
},
"JobName": {
"enum": [
"AssetDelete",
"AssetDeleteCheck",
"AssetDetectFacesQueueAll",
"AssetDetectFaces",
"AssetDetectDuplicatesQueueAll",
"AssetDetectDuplicates",
"AssetEncodeVideoQueueAll",
"AssetEncodeVideo",
"AssetEmptyTrash",
"AssetExtractMetadataQueueAll",
"AssetExtractMetadata",
"AssetFileMigration",
"AssetGenerateThumbnailsQueueAll",
"AssetGenerateThumbnails",
"AuditLogCleanup",
"AuditTableCleanup",
"DatabaseBackup",
"FacialRecognitionQueueAll",
"FacialRecognition",
"FileDelete",
"FileMigrationQueueAll",
"LibraryDeleteCheck",
"LibraryDelete",
"LibraryRemoveAsset",
"LibraryScanAssetsQueueAll",
"LibrarySyncAssets",
"LibrarySyncFilesQueueAll",
"LibrarySyncFiles",
"LibraryScanQueueAll",
"MemoryCleanup",
"MemoryGenerate",
"NotificationsCleanup",
"NotifyUserSignup",
"NotifyAlbumInvite",
"NotifyAlbumUpdate",
"UserDelete",
"UserDeleteCheck",
"UserSyncUsage",
"PersonCleanup",
"PersonFileMigration",
"PersonGenerateThumbnail",
"SessionCleanup",
"SendMail",
"SidecarQueueAll",
"SidecarCheck",
"SidecarWrite",
"SmartSearchQueueAll",
"SmartSearch",
"StorageTemplateMigration",
"StorageTemplateMigrationSingle",
"TagCleanup",
"VersionCheck",
"OcrQueueAll",
"Ocr",
"WorkflowRun"
],
"type": "string"
},
"JobSettingsDto": {
"properties": {
"concurrency": {
@@ -17583,6 +17956,12 @@
"userProfileImage.read",
"userProfileImage.update",
"userProfileImage.delete",
"queue.read",
"queue.update",
"queueJob.create",
"queueJob.read",
"queueJob.update",
"queueJob.delete",
"workflow.create",
"workflow.read",
"workflow.update",
@@ -18083,6 +18462,63 @@
],
"type": "object"
},
"QueueDeleteDto": {
"properties": {
"failed": {
"description": "If true, will also remove failed jobs from the queue.",
"type": "boolean",
"x-immich-history": [
{
"version": "v2.4.0",
"state": "Added"
},
{
"version": "v2.4.0",
"state": "Alpha"
}
],
"x-immich-state": "Alpha"
}
},
"type": "object"
},
"QueueJobResponseDto": {
"properties": {
"data": {
"type": "object"
},
"id": {
"type": "string"
},
"name": {
"allOf": [
{
"$ref": "#/components/schemas/JobName"
}
]
},
"timestamp": {
"type": "integer"
}
},
"required": [
"data",
"name",
"timestamp"
],
"type": "object"
},
"QueueJobStatus": {
"enum": [
"active",
"failed",
"completed",
"delayed",
"waiting",
"paused"
],
"type": "string"
},
"QueueName": {
"enum": [
"thumbnailGeneration",
@@ -18106,12 +18542,35 @@
"type": "string"
},
"QueueResponseDto": {
"properties": {
"isPaused": {
"type": "boolean"
},
"name": {
"allOf": [
{
"$ref": "#/components/schemas/QueueName"
}
]
},
"statistics": {
"$ref": "#/components/schemas/QueueStatisticsDto"
}
},
"required": [
"isPaused",
"name",
"statistics"
],
"type": "object"
},
"QueueResponseLegacyDto": {
"properties": {
"jobCounts": {
"$ref": "#/components/schemas/QueueStatisticsDto"
},
"queueStatus": {
"$ref": "#/components/schemas/QueueStatusDto"
"$ref": "#/components/schemas/QueueStatusLegacyDto"
}
},
"required": [
@@ -18151,7 +18610,7 @@
],
"type": "object"
},
"QueueStatusDto": {
"QueueStatusLegacyDto": {
"properties": {
"isActive": {
"type": "boolean"
@@ -18166,58 +18625,66 @@
],
"type": "object"
},
"QueuesResponseDto": {
"QueueUpdateDto": {
"properties": {
"isPaused": {
"type": "boolean"
}
},
"type": "object"
},
"QueuesResponseLegacyDto": {
"properties": {
"backgroundTask": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"backupDatabase": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"duplicateDetection": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"faceDetection": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"facialRecognition": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"library": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"metadataExtraction": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"migration": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"notifications": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"ocr": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"search": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"sidecar": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"smartSearch": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"storageTemplateMigration": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"thumbnailGeneration": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"videoConversion": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
},
"workflow": {
"$ref": "#/components/schemas/QueueResponseDto"
"$ref": "#/components/schemas/QueueResponseLegacyDto"
}
},
"required": [

View File

@@ -716,32 +716,32 @@ export type QueueStatisticsDto = {
paused: number;
waiting: number;
};
export type QueueStatusDto = {
export type QueueStatusLegacyDto = {
isActive: boolean;
isPaused: boolean;
};
export type QueueResponseDto = {
export type QueueResponseLegacyDto = {
jobCounts: QueueStatisticsDto;
queueStatus: QueueStatusDto;
queueStatus: QueueStatusLegacyDto;
};
export type QueuesResponseDto = {
backgroundTask: QueueResponseDto;
backupDatabase: QueueResponseDto;
duplicateDetection: QueueResponseDto;
faceDetection: QueueResponseDto;
facialRecognition: QueueResponseDto;
library: QueueResponseDto;
metadataExtraction: QueueResponseDto;
migration: QueueResponseDto;
notifications: QueueResponseDto;
ocr: QueueResponseDto;
search: QueueResponseDto;
sidecar: QueueResponseDto;
smartSearch: QueueResponseDto;
storageTemplateMigration: QueueResponseDto;
thumbnailGeneration: QueueResponseDto;
videoConversion: QueueResponseDto;
workflow: QueueResponseDto;
export type QueuesResponseLegacyDto = {
backgroundTask: QueueResponseLegacyDto;
backupDatabase: QueueResponseLegacyDto;
duplicateDetection: QueueResponseLegacyDto;
faceDetection: QueueResponseLegacyDto;
facialRecognition: QueueResponseLegacyDto;
library: QueueResponseLegacyDto;
metadataExtraction: QueueResponseLegacyDto;
migration: QueueResponseLegacyDto;
notifications: QueueResponseLegacyDto;
ocr: QueueResponseLegacyDto;
search: QueueResponseLegacyDto;
sidecar: QueueResponseLegacyDto;
smartSearch: QueueResponseLegacyDto;
storageTemplateMigration: QueueResponseLegacyDto;
thumbnailGeneration: QueueResponseLegacyDto;
videoConversion: QueueResponseLegacyDto;
workflow: QueueResponseLegacyDto;
};
export type JobCreateDto = {
name: ManualJobName;
@@ -966,6 +966,24 @@ export type PluginResponseDto = {
updatedAt: string;
version: string;
};
export type QueueResponseDto = {
isPaused: boolean;
name: QueueName;
statistics: QueueStatisticsDto;
};
export type QueueUpdateDto = {
isPaused?: boolean;
};
export type QueueDeleteDto = {
/** If true, will also remove failed jobs from the queue. */
failed?: boolean;
};
export type QueueJobResponseDto = {
data: object;
id?: string;
name: JobName;
timestamp: number;
};
export type SearchExploreItem = {
data: AssetResponseDto;
value: string;
@@ -2925,7 +2943,7 @@ export function reassignFacesById({ id, faceDto }: {
export function getQueuesLegacy(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueuesResponseDto;
data: QueuesResponseLegacyDto;
}>("/jobs", {
...opts
}));
@@ -2951,7 +2969,7 @@ export function runQueueCommandLegacy({ name, queueCommandDto }: {
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueueResponseDto;
data: QueueResponseLegacyDto;
}>(`/jobs/${encodeURIComponent(name)}`, oazapfts.json({
...opts,
method: "PUT",
@@ -3651,6 +3669,75 @@ export function getPlugin({ id }: {
...opts
}));
}
/**
* List all queues
*/
export function getQueues(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueueResponseDto[];
}>("/queues", {
...opts
}));
}
/**
* Retrieve a queue
*/
export function getQueue({ name }: {
name: QueueName;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueueResponseDto;
}>(`/queues/${encodeURIComponent(name)}`, {
...opts
}));
}
/**
* Update a queue
*/
export function updateQueue({ name, queueUpdateDto }: {
name: QueueName;
queueUpdateDto: QueueUpdateDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueueResponseDto;
}>(`/queues/${encodeURIComponent(name)}`, oazapfts.json({
...opts,
method: "PUT",
body: queueUpdateDto
})));
}
/**
* Empty a queue
*/
export function emptyQueue({ name, queueDeleteDto }: {
name: QueueName;
queueDeleteDto: QueueDeleteDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/queues/${encodeURIComponent(name)}/jobs`, oazapfts.json({
...opts,
method: "DELETE",
body: queueDeleteDto
})));
}
/**
* Retrieve queue jobs
*/
export function getQueueJobs({ name, status }: {
name: QueueName;
status?: QueueJobStatus[];
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: QueueJobResponseDto[];
}>(`/queues/${encodeURIComponent(name)}/jobs${QS.query(QS.explode({
status
}))}`, {
...opts
}));
}
/**
* Retrieve assets by city
*/
@@ -5241,6 +5328,12 @@ export enum Permission {
UserProfileImageRead = "userProfileImage.read",
UserProfileImageUpdate = "userProfileImage.update",
UserProfileImageDelete = "userProfileImage.delete",
QueueRead = "queue.read",
QueueUpdate = "queue.update",
QueueJobCreate = "queueJob.create",
QueueJobRead = "queueJob.read",
QueueJobUpdate = "queueJob.update",
QueueJobDelete = "queueJob.delete",
WorkflowCreate = "workflow.create",
WorkflowRead = "workflow.read",
WorkflowUpdate = "workflow.update",
@@ -5330,6 +5423,71 @@ export enum PluginContext {
Album = "album",
Person = "person"
}
export enum QueueJobStatus {
Active = "active",
Failed = "failed",
Completed = "completed",
Delayed = "delayed",
Waiting = "waiting",
Paused = "paused"
}
export enum JobName {
AssetDelete = "AssetDelete",
AssetDeleteCheck = "AssetDeleteCheck",
AssetDetectFacesQueueAll = "AssetDetectFacesQueueAll",
AssetDetectFaces = "AssetDetectFaces",
AssetDetectDuplicatesQueueAll = "AssetDetectDuplicatesQueueAll",
AssetDetectDuplicates = "AssetDetectDuplicates",
AssetEncodeVideoQueueAll = "AssetEncodeVideoQueueAll",
AssetEncodeVideo = "AssetEncodeVideo",
AssetEmptyTrash = "AssetEmptyTrash",
AssetExtractMetadataQueueAll = "AssetExtractMetadataQueueAll",
AssetExtractMetadata = "AssetExtractMetadata",
AssetFileMigration = "AssetFileMigration",
AssetGenerateThumbnailsQueueAll = "AssetGenerateThumbnailsQueueAll",
AssetGenerateThumbnails = "AssetGenerateThumbnails",
AuditLogCleanup = "AuditLogCleanup",
AuditTableCleanup = "AuditTableCleanup",
DatabaseBackup = "DatabaseBackup",
FacialRecognitionQueueAll = "FacialRecognitionQueueAll",
FacialRecognition = "FacialRecognition",
FileDelete = "FileDelete",
FileMigrationQueueAll = "FileMigrationQueueAll",
LibraryDeleteCheck = "LibraryDeleteCheck",
LibraryDelete = "LibraryDelete",
LibraryRemoveAsset = "LibraryRemoveAsset",
LibraryScanAssetsQueueAll = "LibraryScanAssetsQueueAll",
LibrarySyncAssets = "LibrarySyncAssets",
LibrarySyncFilesQueueAll = "LibrarySyncFilesQueueAll",
LibrarySyncFiles = "LibrarySyncFiles",
LibraryScanQueueAll = "LibraryScanQueueAll",
MemoryCleanup = "MemoryCleanup",
MemoryGenerate = "MemoryGenerate",
NotificationsCleanup = "NotificationsCleanup",
NotifyUserSignup = "NotifyUserSignup",
NotifyAlbumInvite = "NotifyAlbumInvite",
NotifyAlbumUpdate = "NotifyAlbumUpdate",
UserDelete = "UserDelete",
UserDeleteCheck = "UserDeleteCheck",
UserSyncUsage = "UserSyncUsage",
PersonCleanup = "PersonCleanup",
PersonFileMigration = "PersonFileMigration",
PersonGenerateThumbnail = "PersonGenerateThumbnail",
SessionCleanup = "SessionCleanup",
SendMail = "SendMail",
SidecarQueueAll = "SidecarQueueAll",
SidecarCheck = "SidecarCheck",
SidecarWrite = "SidecarWrite",
SmartSearchQueueAll = "SmartSearchQueueAll",
SmartSearch = "SmartSearch",
StorageTemplateMigration = "StorageTemplateMigration",
StorageTemplateMigrationSingle = "StorageTemplateMigrationSingle",
TagCleanup = "TagCleanup",
VersionCheck = "VersionCheck",
OcrQueueAll = "OcrQueueAll",
Ocr = "Ocr",
WorkflowRun = "WorkflowRun"
}
export enum SearchSuggestionType {
Country = "country",
State = "state",

View File

@@ -163,6 +163,8 @@ export const endpointTags: Record<ApiTag, string> = {
'A person is a collection of faces, which can be favorited and named. A person can also be merged into another person. People are automatically created via the face recognition job.',
[ApiTag.Plugins]:
'A plugin is an installed module that makes filters and actions available for the workflow feature.',
[ApiTag.Queues]:
'Queues and background jobs are used for processing tasks asynchronously. Queues can be paused and resumed as needed.',
[ApiTag.Search]:
'Endpoints related to searching assets via text, smart search, optical character recognition (OCR), and other filters like person, album, and other metadata. Search endpoints usually support pagination and sorting.',
[ApiTag.Server]:

View File

@@ -20,6 +20,7 @@ import { OAuthController } from 'src/controllers/oauth.controller';
import { PartnerController } from 'src/controllers/partner.controller';
import { PersonController } from 'src/controllers/person.controller';
import { PluginController } from 'src/controllers/plugin.controller';
import { QueueController } from 'src/controllers/queue.controller';
import { SearchController } from 'src/controllers/search.controller';
import { ServerController } from 'src/controllers/server.controller';
import { SessionController } from 'src/controllers/session.controller';
@@ -59,6 +60,7 @@ export const controllers = [
PartnerController,
PersonController,
PluginController,
QueueController,
SearchController,
ServerController,
SessionController,

View File

@@ -1,10 +1,12 @@
import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Endpoint, HistoryBuilder } from 'src/decorators';
import { AuthDto } from 'src/dtos/auth.dto';
import { JobCreateDto } from 'src/dtos/job.dto';
import { QueueCommandDto, QueueNameParamDto, QueueResponseDto, QueuesResponseDto } from 'src/dtos/queue.dto';
import { QueueResponseLegacyDto, QueuesResponseLegacyDto } from 'src/dtos/queue-legacy.dto';
import { QueueCommandDto, QueueNameParamDto } from 'src/dtos/queue.dto';
import { ApiTag, Permission } from 'src/enum';
import { Authenticated } from 'src/middleware/auth.guard';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { JobService } from 'src/services/job.service';
import { QueueService } from 'src/services/queue.service';
@@ -21,10 +23,10 @@ export class JobController {
@Endpoint({
summary: 'Retrieve queue counts and status',
description: 'Retrieve the counts of the current queue, as well as the current status.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
history: new HistoryBuilder().added('v1').beta('v1').stable('v2').deprecated('v2.4.0'),
})
getQueuesLegacy(): Promise<QueuesResponseDto> {
return this.queueService.getAll();
getQueuesLegacy(@Auth() auth: AuthDto): Promise<QueuesResponseLegacyDto> {
return this.queueService.getAllLegacy(auth);
}
@Post()
@@ -46,9 +48,12 @@ export class JobController {
summary: 'Run jobs',
description:
'Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
history: new HistoryBuilder().added('v1').beta('v1').stable('v2').deprecated('v2.4.0'),
})
runQueueCommandLegacy(@Param() { name }: QueueNameParamDto, @Body() dto: QueueCommandDto): Promise<QueueResponseDto> {
return this.queueService.runCommand(name, dto);
runQueueCommandLegacy(
@Param() { name }: QueueNameParamDto,
@Body() dto: QueueCommandDto,
): Promise<QueueResponseLegacyDto> {
return this.queueService.runCommandLegacy(name, dto);
}
}

View File

@@ -0,0 +1,85 @@
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Put, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Endpoint, HistoryBuilder } from 'src/decorators';
import { AuthDto } from 'src/dtos/auth.dto';
import {
QueueDeleteDto,
QueueJobResponseDto,
QueueJobSearchDto,
QueueNameParamDto,
QueueResponseDto,
QueueUpdateDto,
} from 'src/dtos/queue.dto';
import { ApiTag, Permission } from 'src/enum';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { QueueService } from 'src/services/queue.service';
@ApiTags(ApiTag.Queues)
@Controller('queues')
export class QueueController {
constructor(private service: QueueService) {}
@Get()
@Authenticated({ permission: Permission.QueueRead, admin: true })
@Endpoint({
summary: 'List all queues',
description: 'Retrieves a list of queues.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
getQueues(@Auth() auth: AuthDto): Promise<QueueResponseDto[]> {
return this.service.getAll(auth);
}
@Get(':name')
@Authenticated({ permission: Permission.QueueRead, admin: true })
@Endpoint({
summary: 'Retrieve a queue',
description: 'Retrieves a specific queue by its name.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
getQueue(@Auth() auth: AuthDto, @Param() { name }: QueueNameParamDto): Promise<QueueResponseDto> {
return this.service.get(auth, name);
}
@Put(':name')
@Authenticated({ permission: Permission.QueueUpdate, admin: true })
@Endpoint({
summary: 'Update a queue',
description: 'Change the paused status of a specific queue.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
updateQueue(
@Auth() auth: AuthDto,
@Param() { name }: QueueNameParamDto,
@Body() dto: QueueUpdateDto,
): Promise<QueueResponseDto> {
return this.service.update(auth, name, dto);
}
@Get(':name/jobs')
@Authenticated({ permission: Permission.QueueJobRead, admin: true })
@Endpoint({
summary: 'Retrieve queue jobs',
description: 'Retrieves a list of queue jobs from the specified queue.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
getQueueJobs(
@Auth() auth: AuthDto,
@Param() { name }: QueueNameParamDto,
@Query() dto: QueueJobSearchDto,
): Promise<QueueJobResponseDto[]> {
return this.service.searchJobs(auth, name, dto);
}
@Delete(':name/jobs')
@Authenticated({ permission: Permission.QueueJobDelete, admin: true })
@HttpCode(HttpStatus.NO_CONTENT)
@Endpoint({
summary: 'Empty a queue',
description: 'Removes all jobs from the specified queue.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
emptyQueue(@Auth() auth: AuthDto, @Param() { name }: QueueNameParamDto, @Body() dto: QueueDeleteDto): Promise<void> {
return this.service.emptyQueue(auth, name, dto);
}
}

View File

@@ -0,0 +1,89 @@
import { ApiProperty } from '@nestjs/swagger';
import { QueueResponseDto, QueueStatisticsDto } from 'src/dtos/queue.dto';
import { QueueName } from 'src/enum';
export class QueueStatusLegacyDto {
isActive!: boolean;
isPaused!: boolean;
}
export class QueueResponseLegacyDto {
@ApiProperty({ type: QueueStatusLegacyDto })
queueStatus!: QueueStatusLegacyDto;
@ApiProperty({ type: QueueStatisticsDto })
jobCounts!: QueueStatisticsDto;
}
export class QueuesResponseLegacyDto implements Record<QueueName, QueueResponseLegacyDto> {
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.ThumbnailGeneration]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.MetadataExtraction]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.VideoConversion]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.SmartSearch]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.StorageTemplateMigration]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Migration]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.BackgroundTask]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Search]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.DuplicateDetection]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.FaceDetection]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.FacialRecognition]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Sidecar]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Library]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Notification]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.BackupDatabase]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Ocr]!: QueueResponseLegacyDto;
@ApiProperty({ type: QueueResponseLegacyDto })
[QueueName.Workflow]!: QueueResponseLegacyDto;
}
export const mapQueueLegacy = (response: QueueResponseDto): QueueResponseLegacyDto => {
return {
queueStatus: {
isPaused: response.isPaused,
isActive: response.statistics.active > 0,
},
jobCounts: response.statistics,
};
};
export const mapQueuesLegacy = (responses: QueueResponseDto[]): QueuesResponseLegacyDto => {
const legacy = new QueuesResponseLegacyDto();
for (const response of responses) {
legacy[response.name] = mapQueueLegacy(response);
}
return legacy;
};

View File

@@ -1,5 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
import { QueueCommand, QueueName } from 'src/enum';
import { HistoryBuilder, Property } from 'src/decorators';
import { JobName, QueueCommand, QueueJobStatus, QueueName } from 'src/enum';
import { ValidateBoolean, ValidateEnum } from 'src/validation';
export class QueueNameParamDto {
@@ -15,6 +16,46 @@ export class QueueCommandDto {
force?: boolean; // TODO: this uses undefined as a third state, which should be refactored to be more explicit
}
export class QueueUpdateDto {
@ValidateBoolean({ optional: true })
isPaused?: boolean;
}
export class QueueDeleteDto {
@ValidateBoolean({ optional: true })
@Property({
description: 'If true, will also remove failed jobs from the queue.',
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
})
failed?: boolean;
}
export class QueueJobSearchDto {
@ValidateEnum({ enum: QueueJobStatus, name: 'QueueJobStatus', optional: true, each: true })
status?: QueueJobStatus[];
}
export class QueueJobResponseDto {
id?: string;
@ValidateEnum({ enum: JobName, name: 'JobName' })
name!: JobName;
data!: object;
@ApiProperty({ type: 'integer' })
timestamp!: number;
}
export class QueueResponseDto {
@ValidateEnum({ enum: QueueName, name: 'QueueName' })
name!: QueueName;
@ValidateBoolean()
isPaused!: boolean;
statistics!: QueueStatisticsDto;
}
export class QueueStatisticsDto {
@ApiProperty({ type: 'integer' })
active!: number;
@@ -29,69 +70,3 @@ export class QueueStatisticsDto {
@ApiProperty({ type: 'integer' })
paused!: number;
}
export class QueueStatusDto {
isActive!: boolean;
isPaused!: boolean;
}
export class QueueResponseDto {
@ApiProperty({ type: QueueStatisticsDto })
jobCounts!: QueueStatisticsDto;
@ApiProperty({ type: QueueStatusDto })
queueStatus!: QueueStatusDto;
}
export class QueuesResponseDto implements Record<QueueName, QueueResponseDto> {
@ApiProperty({ type: QueueResponseDto })
[QueueName.ThumbnailGeneration]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.MetadataExtraction]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.VideoConversion]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.SmartSearch]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.StorageTemplateMigration]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Migration]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.BackgroundTask]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Search]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.DuplicateDetection]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.FaceDetection]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.FacialRecognition]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Sidecar]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Library]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Notification]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.BackupDatabase]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Ocr]!: QueueResponseDto;
@ApiProperty({ type: QueueResponseDto })
[QueueName.Workflow]!: QueueResponseDto;
}

View File

@@ -248,6 +248,14 @@ export enum Permission {
UserProfileImageUpdate = 'userProfileImage.update',
UserProfileImageDelete = 'userProfileImage.delete',
QueueRead = 'queue.read',
QueueUpdate = 'queue.update',
QueueJobCreate = 'queueJob.create',
QueueJobRead = 'queueJob.read',
QueueJobUpdate = 'queueJob.update',
QueueJobDelete = 'queueJob.delete',
WorkflowCreate = 'workflow.create',
WorkflowRead = 'workflow.read',
WorkflowUpdate = 'workflow.update',
@@ -543,6 +551,15 @@ export enum QueueName {
Workflow = 'workflow',
}
export enum QueueJobStatus {
Active = 'active',
Failed = 'failed',
Complete = 'completed',
Delayed = 'delayed',
Waiting = 'waiting',
Paused = 'paused',
}
export enum JobName {
AssetDelete = 'AssetDelete',
AssetDeleteCheck = 'AssetDeleteCheck',
@@ -624,9 +641,13 @@ export enum JobName {
export enum QueueCommand {
Start = 'start',
/** @deprecated Use `updateQueue` instead */
Pause = 'pause',
/** @deprecated Use `updateQueue` instead */
Resume = 'resume',
/** @deprecated Use `emptyQueue` instead */
Empty = 'empty',
/** @deprecated Use `emptyQueue` instead */
ClearFailed = 'clear-failed',
}
@@ -823,6 +844,7 @@ export enum ApiTag {
Partners = 'Partners',
People = 'People',
Plugins = 'Plugins',
Queues = 'Queues',
Search = 'Search',
Server = 'Server',
Sessions = 'Sessions',

View File

@@ -249,7 +249,7 @@ const getEnv = (): EnvData => {
prefix: 'immich_bull',
connection: { ...redisConfig },
defaultJobOptions: {
attempts: 3,
attempts: 1,
removeOnComplete: true,
removeOnFail: false,
},

View File

@@ -5,11 +5,12 @@ import { JobsOptions, Queue, Worker } from 'bullmq';
import { ClassConstructor } from 'class-transformer';
import { setTimeout } from 'node:timers/promises';
import { JobConfig } from 'src/decorators';
import { JobName, JobStatus, MetadataKey, QueueCleanType, QueueName } from 'src/enum';
import { QueueJobResponseDto, QueueJobSearchDto } from 'src/dtos/queue.dto';
import { JobName, JobStatus, MetadataKey, QueueCleanType, QueueJobStatus, QueueName } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository';
import { EventRepository } from 'src/repositories/event.repository';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { JobCounts, JobItem, JobOf, QueueStatus } from 'src/types';
import { JobCounts, JobItem, JobOf } from 'src/types';
import { getKeyByValue, getMethodNames, ImmichStartupError } from 'src/utils/misc';
type JobMapItem = {
@@ -115,13 +116,14 @@ export class JobRepository {
worker.concurrency = concurrency;
}
async getQueueStatus(name: QueueName): Promise<QueueStatus> {
async isActive(name: QueueName): Promise<boolean> {
const queue = this.getQueue(name);
const count = await queue.getActiveCount();
return count > 0;
}
return {
isActive: !!(await queue.getActiveCount()),
isPaused: await queue.isPaused(),
};
async isPaused(name: QueueName): Promise<boolean> {
return this.getQueue(name).isPaused();
}
pause(name: QueueName) {
@@ -192,17 +194,28 @@ export class JobRepository {
}
async waitForQueueCompletion(...queues: QueueName[]): Promise<void> {
let activeQueue: QueueStatus | undefined;
do {
const statuses = await Promise.all(queues.map((name) => this.getQueueStatus(name)));
activeQueue = statuses.find((status) => status.isActive);
} while (activeQueue);
{
this.logger.verbose(`Waiting for ${activeQueue} queue to stop...`);
const getPending = async () => {
const results = await Promise.all(queues.map(async (name) => ({ pending: await this.isActive(name), name })));
return results.filter(({ pending }) => pending).map(({ name }) => name);
};
let pending = await getPending();
while (pending.length > 0) {
this.logger.verbose(`Waiting for ${pending[0]} queue to stop...`);
await setTimeout(1000);
pending = await getPending();
}
}
async searchJobs(name: QueueName, dto: QueueJobSearchDto): Promise<QueueJobResponseDto[]> {
const jobs = await this.getQueue(name).getJobs(dto.status ?? Object.values(QueueJobStatus), 0, 1000);
return jobs.map((job) => {
const { id, name, timestamp, data } = job;
return { id, name: name as JobName, timestamp, data };
});
}
private getJobOptions(item: JobItem): JobsOptions | null {
switch (item.name) {
case JobName.NotifyAlbumUpdate: {

View File

@@ -2,6 +2,7 @@ import { BadRequestException } from '@nestjs/common';
import { defaults, SystemConfig } from 'src/config';
import { ImmichWorker, JobName, QueueCommand, QueueName } from 'src/enum';
import { QueueService } from 'src/services/queue.service';
import { factory } from 'test/small.factory';
import { newTestService, ServiceMocks } from 'test/utils';
describe(QueueService.name, () => {
@@ -52,80 +53,64 @@ describe(QueueService.name, () => {
describe('getAllJobStatus', () => {
it('should get all job statuses', async () => {
mocks.job.getJobCounts.mockResolvedValue({
active: 1,
completed: 1,
failed: 1,
delayed: 1,
waiting: 1,
paused: 1,
});
mocks.job.getQueueStatus.mockResolvedValue({
isActive: true,
isPaused: true,
});
const stats = factory.queueStatistics({ active: 1 });
const expected = { jobCounts: stats, queueStatus: { isActive: true, isPaused: true } };
const expectedJobStatus = {
jobCounts: {
active: 1,
completed: 1,
delayed: 1,
failed: 1,
waiting: 1,
paused: 1,
},
queueStatus: {
isActive: true,
isPaused: true,
},
};
mocks.job.getJobCounts.mockResolvedValue(stats);
mocks.job.isPaused.mockResolvedValue(true);
await expect(sut.getAll()).resolves.toEqual({
[QueueName.BackgroundTask]: expectedJobStatus,
[QueueName.DuplicateDetection]: expectedJobStatus,
[QueueName.SmartSearch]: expectedJobStatus,
[QueueName.MetadataExtraction]: expectedJobStatus,
[QueueName.Search]: expectedJobStatus,
[QueueName.StorageTemplateMigration]: expectedJobStatus,
[QueueName.Migration]: expectedJobStatus,
[QueueName.ThumbnailGeneration]: expectedJobStatus,
[QueueName.VideoConversion]: expectedJobStatus,
[QueueName.FaceDetection]: expectedJobStatus,
[QueueName.FacialRecognition]: expectedJobStatus,
[QueueName.Sidecar]: expectedJobStatus,
[QueueName.Library]: expectedJobStatus,
[QueueName.Notification]: expectedJobStatus,
[QueueName.BackupDatabase]: expectedJobStatus,
[QueueName.Ocr]: expectedJobStatus,
[QueueName.Workflow]: expectedJobStatus,
await expect(sut.getAllLegacy(factory.auth())).resolves.toEqual({
[QueueName.BackgroundTask]: expected,
[QueueName.DuplicateDetection]: expected,
[QueueName.SmartSearch]: expected,
[QueueName.MetadataExtraction]: expected,
[QueueName.Search]: expected,
[QueueName.StorageTemplateMigration]: expected,
[QueueName.Migration]: expected,
[QueueName.ThumbnailGeneration]: expected,
[QueueName.VideoConversion]: expected,
[QueueName.FaceDetection]: expected,
[QueueName.FacialRecognition]: expected,
[QueueName.Sidecar]: expected,
[QueueName.Library]: expected,
[QueueName.Notification]: expected,
[QueueName.BackupDatabase]: expected,
[QueueName.Ocr]: expected,
[QueueName.Workflow]: expected,
});
});
});
describe('handleCommand', () => {
it('should handle a pause command', async () => {
await sut.runCommand(QueueName.MetadataExtraction, { command: QueueCommand.Pause, force: false });
mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics());
await sut.runCommandLegacy(QueueName.MetadataExtraction, { command: QueueCommand.Pause, force: false });
expect(mocks.job.pause).toHaveBeenCalledWith(QueueName.MetadataExtraction);
});
it('should handle a resume command', async () => {
await sut.runCommand(QueueName.MetadataExtraction, { command: QueueCommand.Resume, force: false });
mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics());
await sut.runCommandLegacy(QueueName.MetadataExtraction, { command: QueueCommand.Resume, force: false });
expect(mocks.job.resume).toHaveBeenCalledWith(QueueName.MetadataExtraction);
});
it('should handle an empty command', async () => {
await sut.runCommand(QueueName.MetadataExtraction, { command: QueueCommand.Empty, force: false });
mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics());
await sut.runCommandLegacy(QueueName.MetadataExtraction, { command: QueueCommand.Empty, force: false });
expect(mocks.job.empty).toHaveBeenCalledWith(QueueName.MetadataExtraction);
});
it('should not start a job that is already running', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: true, isPaused: false });
mocks.job.isActive.mockResolvedValue(true);
await expect(
sut.runCommand(QueueName.VideoConversion, { command: QueueCommand.Start, force: false }),
sut.runCommandLegacy(QueueName.VideoConversion, { command: QueueCommand.Start, force: false }),
).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.job.queue).not.toHaveBeenCalled();
@@ -133,33 +118,37 @@ describe(QueueService.name, () => {
});
it('should handle a start video conversion command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
mocks.job.isActive.mockResolvedValue(false);
mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics());
await sut.runCommand(QueueName.VideoConversion, { command: QueueCommand.Start, force: false });
await sut.runCommandLegacy(QueueName.VideoConversion, { command: QueueCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.AssetEncodeVideoQueueAll, data: { force: false } });
});
it('should handle a start storage template migration command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
mocks.job.isActive.mockResolvedValue(false);
mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics());
await sut.runCommand(QueueName.StorageTemplateMigration, { command: QueueCommand.Start, force: false });
await sut.runCommandLegacy(QueueName.StorageTemplateMigration, { command: QueueCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.StorageTemplateMigration });
});
it('should handle a start smart search command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
mocks.job.isActive.mockResolvedValue(false);
mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics());
await sut.runCommand(QueueName.SmartSearch, { command: QueueCommand.Start, force: false });
await sut.runCommandLegacy(QueueName.SmartSearch, { command: QueueCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.SmartSearchQueueAll, data: { force: false } });
});
it('should handle a start metadata extraction command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
mocks.job.isActive.mockResolvedValue(false);
mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics());
await sut.runCommand(QueueName.MetadataExtraction, { command: QueueCommand.Start, force: false });
await sut.runCommandLegacy(QueueName.MetadataExtraction, { command: QueueCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({
name: JobName.AssetExtractMetadataQueueAll,
@@ -168,17 +157,19 @@ describe(QueueService.name, () => {
});
it('should handle a start sidecar command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
mocks.job.isActive.mockResolvedValue(false);
mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics());
await sut.runCommand(QueueName.Sidecar, { command: QueueCommand.Start, force: false });
await sut.runCommandLegacy(QueueName.Sidecar, { command: QueueCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.SidecarQueueAll, data: { force: false } });
});
it('should handle a start thumbnail generation command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
mocks.job.isActive.mockResolvedValue(false);
mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics());
await sut.runCommand(QueueName.ThumbnailGeneration, { command: QueueCommand.Start, force: false });
await sut.runCommandLegacy(QueueName.ThumbnailGeneration, { command: QueueCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({
name: JobName.AssetGenerateThumbnailsQueueAll,
@@ -187,34 +178,37 @@ describe(QueueService.name, () => {
});
it('should handle a start face detection command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
mocks.job.isActive.mockResolvedValue(false);
mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics());
await sut.runCommand(QueueName.FaceDetection, { command: QueueCommand.Start, force: false });
await sut.runCommandLegacy(QueueName.FaceDetection, { command: QueueCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.AssetDetectFacesQueueAll, data: { force: false } });
});
it('should handle a start facial recognition command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
mocks.job.isActive.mockResolvedValue(false);
mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics());
await sut.runCommand(QueueName.FacialRecognition, { command: QueueCommand.Start, force: false });
await sut.runCommandLegacy(QueueName.FacialRecognition, { command: QueueCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.FacialRecognitionQueueAll, data: { force: false } });
});
it('should handle a start backup database command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
mocks.job.isActive.mockResolvedValue(false);
mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics());
await sut.runCommand(QueueName.BackupDatabase, { command: QueueCommand.Start, force: false });
await sut.runCommandLegacy(QueueName.BackupDatabase, { command: QueueCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.DatabaseBackup, data: { force: false } });
});
it('should throw a bad request when an invalid queue is used', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
mocks.job.isActive.mockResolvedValue(false);
await expect(
sut.runCommand(QueueName.BackgroundTask, { command: QueueCommand.Start, force: false }),
sut.runCommandLegacy(QueueName.BackgroundTask, { command: QueueCommand.Start, force: false }),
).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.job.queue).not.toHaveBeenCalled();

View File

@@ -2,7 +2,21 @@ import { BadRequestException, Injectable } from '@nestjs/common';
import { ClassConstructor } from 'class-transformer';
import { SystemConfig } from 'src/config';
import { OnEvent } from 'src/decorators';
import { QueueCommandDto, QueueResponseDto, QueuesResponseDto } from 'src/dtos/queue.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import {
mapQueueLegacy,
mapQueuesLegacy,
QueueResponseLegacyDto,
QueuesResponseLegacyDto,
} from 'src/dtos/queue-legacy.dto';
import {
QueueCommandDto,
QueueDeleteDto,
QueueJobResponseDto,
QueueJobSearchDto,
QueueResponseDto,
QueueUpdateDto,
} from 'src/dtos/queue.dto';
import {
BootstrapEventPriority,
CronJob,
@@ -86,7 +100,7 @@ export class QueueService extends BaseService {
this.services = services;
}
async runCommand(name: QueueName, dto: QueueCommandDto): Promise<QueueResponseDto> {
async runCommandLegacy(name: QueueName, dto: QueueCommandDto): Promise<QueueResponseLegacyDto> {
this.logger.debug(`Handling command: queue=${name},command=${dto.command},force=${dto.force}`);
switch (dto.command) {
@@ -117,28 +131,60 @@ export class QueueService extends BaseService {
}
}
const response = await this.getByName(name);
return mapQueueLegacy(response);
}
async getAll(_auth: AuthDto): Promise<QueueResponseDto[]> {
return Promise.all(Object.values(QueueName).map((name) => this.getByName(name)));
}
async getAllLegacy(auth: AuthDto): Promise<QueuesResponseLegacyDto> {
const responses = await this.getAll(auth);
return mapQueuesLegacy(responses);
}
get(auth: AuthDto, name: QueueName): Promise<QueueResponseDto> {
return this.getByName(name);
}
async getAll(): Promise<QueuesResponseDto> {
const response = new QueuesResponseDto();
for (const name of Object.values(QueueName)) {
response[name] = await this.getByName(name);
async update(auth: AuthDto, name: QueueName, dto: QueueUpdateDto): Promise<QueueResponseDto> {
if (dto.isPaused === true) {
if (name === QueueName.BackgroundTask) {
throw new BadRequestException(`The BackgroundTask queue cannot be paused`);
}
await this.jobRepository.pause(name);
}
return response;
if (dto.isPaused === false) {
await this.jobRepository.resume(name);
}
return this.getByName(name);
}
async getByName(name: QueueName): Promise<QueueResponseDto> {
const [jobCounts, queueStatus] = await Promise.all([
this.jobRepository.getJobCounts(name),
this.jobRepository.getQueueStatus(name),
]);
searchJobs(auth: AuthDto, name: QueueName, dto: QueueJobSearchDto): Promise<QueueJobResponseDto[]> {
return this.jobRepository.searchJobs(name, dto);
}
return { jobCounts, queueStatus };
async emptyQueue(auth: AuthDto, name: QueueName, dto: QueueDeleteDto) {
await this.jobRepository.empty(name);
if (dto.failed) {
await this.jobRepository.clear(name, QueueCleanType.Failed);
}
}
private async getByName(name: QueueName): Promise<QueueResponseDto> {
const [statistics, isPaused] = await Promise.all([
this.jobRepository.getJobCounts(name),
this.jobRepository.isPaused(name),
]);
return { name, isPaused, statistics };
}
private async start(name: QueueName, { force }: QueueCommandDto): Promise<void> {
const { isActive } = await this.jobRepository.getQueueStatus(name);
const isActive = await this.jobRepository.isActive(name);
if (isActive) {
throw new BadRequestException(`Job is already running`);
}

View File

@@ -291,11 +291,6 @@ export interface JobCounts {
paused: number;
}
export interface QueueStatus {
isActive: boolean;
isPaused: boolean;
}
export type JobItem =
// Audit
| { name: JobName.AuditTableCleanup; data?: IBaseJob }

View File

@@ -11,9 +11,11 @@ export const newJobRepositoryMock = (): Mocked<RepositoryInterface<JobRepository
empty: vitest.fn(),
pause: vitest.fn(),
resume: vitest.fn(),
searchJobs: vitest.fn(),
queue: vitest.fn().mockImplementation(() => Promise.resolve()),
queueAll: vitest.fn().mockImplementation(() => Promise.resolve()),
getQueueStatus: vitest.fn(),
isActive: vitest.fn(),
isPaused: vitest.fn(),
getJobCounts: vitest.fn(),
clear: vitest.fn(),
waitForQueueCompletion: vitest.fn(),

View File

@@ -14,6 +14,7 @@ import {
} from 'src/database';
import { MapAsset } from 'src/dtos/asset-response.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { QueueStatisticsDto } from 'src/dtos/queue.dto';
import { AssetStatus, AssetType, AssetVisibility, MemoryType, Permission, UserMetadataKey, UserStatus } from 'src/enum';
import { OnThisDayData, UserMetadataItem } from 'src/types';
import { v4, v7 } from 'uuid';
@@ -139,6 +140,16 @@ const sessionFactory = (session: Partial<Session> = {}) => ({
...session,
});
const queueStatisticsFactory = (dto?: Partial<QueueStatisticsDto>) => ({
active: 0,
completed: 0,
failed: 0,
delayed: 0,
waiting: 0,
paused: 0,
...dto,
});
const stackFactory = () => ({
id: newUuid(),
ownerId: newUuid(),
@@ -353,6 +364,7 @@ export const factory = {
library: libraryFactory,
memory: memoryFactory,
partner: partnerFactory,
queueStatistics: queueStatisticsFactory,
session: sessionFactory,
stack: stackFactory,
user: userFactory,

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import Badge from '$lib/elements/Badge.svelte';
import { locale } from '$lib/stores/preferences.store';
import { QueueCommand, type QueueCommandDto, type QueueStatisticsDto, type QueueStatusDto } from '@immich/sdk';
import { QueueCommand, type QueueCommandDto, type QueueStatisticsDto, type QueueStatusLegacyDto } from '@immich/sdk';
import { Icon, IconButton } from '@immich/ui';
import {
mdiAlertCircle,
@@ -23,7 +23,7 @@
subtitle: string | undefined;
description: Component | undefined;
statistics: QueueStatisticsDto;
queueStatus: QueueStatusDto;
queueStatus: QueueStatusLegacyDto;
icon: string;
disabled?: boolean;
allText: string | undefined;

View File

@@ -6,7 +6,7 @@
QueueCommand,
type QueueCommandDto,
QueueName,
type QueuesResponseDto,
type QueuesResponseLegacyDto,
runQueueCommandLegacy,
} from '@immich/sdk';
import { modalManager, toastManager } from '@immich/ui';
@@ -29,7 +29,7 @@
import StorageMigrationDescription from './StorageMigrationDescription.svelte';
interface Props {
jobs: QueuesResponseDto;
jobs: QueuesResponseLegacyDto;
}
let { jobs = $bindable() }: Props = $props();

View File

@@ -5,7 +5,13 @@
import JobCreateModal from '$lib/modals/JobCreateModal.svelte';
import { asyncTimeout } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { getQueuesLegacy, QueueCommand, QueueName, runQueueCommandLegacy, type QueuesResponseDto } from '@immich/sdk';
import {
getQueuesLegacy,
QueueCommand,
QueueName,
runQueueCommandLegacy,
type QueuesResponseLegacyDto,
} from '@immich/sdk';
import { Button, HStack, modalManager, Text } from '@immich/ui';
import { mdiCog, mdiPlay, mdiPlus } from '@mdi/js';
import { onDestroy, onMount } from 'svelte';
@@ -18,7 +24,7 @@
let { data }: Props = $props();
let jobs: QueuesResponseDto | undefined = $state();
let jobs: QueuesResponseLegacyDto | undefined = $state();
let running = true;