From ddbfee33d11f63ab372503fb3ea4cf9cc6009187 Mon Sep 17 00:00:00 2001 From: Flaminel Date: Fri, 6 Jun 2025 22:44:08 +0300 Subject: [PATCH] fixed queue cleaner config retrieval --- .../QueueCleaner/QueueCleanerConfigDto.cs | 5 - .../QueueCleaner/QueueCleanerConfig.cs | 1 + .../Controllers/ConfigurationController.cs | 8 +- code/Executable/Jobs/BackgroundJobManager.cs | 4 +- .../Services/JobManagementService.cs | 28 +-- .../ContentBlocker/ContentBlocker.cs | 187 ----------------- .../core/services/configuration.service.ts | 196 ++++-------------- 7 files changed, 60 insertions(+), 369 deletions(-) delete mode 100644 code/Infrastructure/Verticals/ContentBlocker/ContentBlocker.cs diff --git a/code/Common/Configuration/DTOs/QueueCleaner/QueueCleanerConfigDto.cs b/code/Common/Configuration/DTOs/QueueCleaner/QueueCleanerConfigDto.cs index 21b2fe2c..173cef31 100644 --- a/code/Common/Configuration/DTOs/QueueCleaner/QueueCleanerConfigDto.cs +++ b/code/Common/Configuration/DTOs/QueueCleaner/QueueCleanerConfigDto.cs @@ -22,11 +22,6 @@ public class QueueCleanerConfigDto /// public bool RunSequentially { get; set; } - /// - /// Path to ignored downloads file - /// - public string IgnoredDownloadsPath { get; set; } = string.Empty; - /// /// Maximum number of strikes for failed imports /// diff --git a/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs b/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs index bd1b2ad5..ec71abd7 100644 --- a/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs +++ b/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs @@ -12,6 +12,7 @@ public sealed record QueueCleanerConfig : IJobConfig public string CronExpression { get; init; } = "0 0/5 * * * ?"; + // TODO public string IgnoredDownloadsPath { get; init; } = string.Empty; public FailedImportConfig FailedImport { get; init; } = new(); diff --git a/code/Executable/Controllers/ConfigurationController.cs b/code/Executable/Controllers/ConfigurationController.cs index 46d42bb3..de0ca4a0 100644 --- a/code/Executable/Controllers/ConfigurationController.cs +++ b/code/Executable/Controllers/ConfigurationController.cs @@ -158,22 +158,24 @@ public class ConfigurationController : ControllerBase if (!string.IsNullOrEmpty(config.CronExpression)) { // If the job is enabled, update its schedule with the configured cron expression - _logger.LogInformation("{JobName} is enabled, updating job schedule with cron expression: {CronExpression}", + _logger.LogInformation("{name} is enabled, updating job schedule with cron expression: {CronExpression}", jobType.ToString(), config.CronExpression); + _logger.LogCritical("This is a random test log"); + // Create a Quartz job schedule with the cron expression await _jobManagementService.StartJob(jobType, null, config.CronExpression); } else { - _logger.LogWarning("{JobName} is enabled, but no cron expression was found in the configuration", jobType.ToString()); + _logger.LogWarning("{name} is enabled, but no cron expression was found in the configuration", jobType.ToString()); } return; } // If the job is disabled, stop it - _logger.LogInformation("{JobName} is disabled, stopping the job", jobType.ToString()); + _logger.LogInformation("{name} is disabled, stopping the job", jobType.ToString()); await _jobManagementService.StopJob(jobType); } diff --git a/code/Executable/Jobs/BackgroundJobManager.cs b/code/Executable/Jobs/BackgroundJobManager.cs index 93e5c437..ce079552 100644 --- a/code/Executable/Jobs/BackgroundJobManager.cs +++ b/code/Executable/Jobs/BackgroundJobManager.cs @@ -188,7 +188,7 @@ public class BackgroundJobManager : IHostedService await _scheduler.ScheduleJob(trigger, cancellationToken); await _scheduler.ScheduleJob(startupTrigger, cancellationToken); - _logger.LogInformation("Added job {JobName} with cron expression {CronExpression}", + _logger.LogInformation("Added job {name} with cron expression {CronExpression}", typeName, cronExpression); } @@ -215,6 +215,6 @@ public class BackgroundJobManager : IHostedService // Add job to scheduler await _scheduler.AddJob(jobDetail, true, cancellationToken); - _logger.LogInformation("Added job {JobName} without trigger (will be chained)", typeName); + _logger.LogInformation("Added job {name} without trigger (will be chained)", typeName); } } diff --git a/code/Infrastructure/Services/JobManagementService.cs b/code/Infrastructure/Services/JobManagementService.cs index 757f3d8b..0394d951 100644 --- a/code/Infrastructure/Services/JobManagementService.cs +++ b/code/Infrastructure/Services/JobManagementService.cs @@ -34,7 +34,7 @@ public class JobManagementService : IJobManagementService // Check if job exists if (!await scheduler.CheckExists(jobKey)) { - _logger.LogError("Job {jobName} does not exist", jobName); + _logger.LogError("Job {name} does not exist", jobName); return false; } @@ -86,7 +86,7 @@ public class JobManagementService : IJobManagementService // Resume the job if it's paused await scheduler.ResumeJob(jobKey); - _logger.LogInformation("Job {jobName} started successfully", jobName); + _logger.LogInformation("Job {name} started successfully", jobName); return true; } catch (Exception ex) @@ -106,7 +106,7 @@ public class JobManagementService : IJobManagementService if (!await scheduler.CheckExists(jobKey)) { - _logger.LogError("Job {jobName} does not exist", jobName); + _logger.LogError("Job {name} does not exist", jobName); return false; } @@ -117,7 +117,7 @@ public class JobManagementService : IJobManagementService await scheduler.UnscheduleJob(trigger.Key); } - _logger.LogInformation("Job {jobName} stopped successfully", jobName); + _logger.LogInformation("Job {name} stopped successfully", jobName); return true; } catch (Exception ex) @@ -137,12 +137,12 @@ public class JobManagementService : IJobManagementService if (!await scheduler.CheckExists(jobKey)) { - _logger.LogError("Job {jobName} does not exist", jobName); + _logger.LogError("Job {name} does not exist", jobName); return false; } await scheduler.PauseJob(jobKey); - _logger.LogInformation("Job {jobName} paused successfully", jobName); + _logger.LogInformation("Job {name} paused successfully", jobName); return true; } catch (Exception ex) @@ -162,17 +162,17 @@ public class JobManagementService : IJobManagementService if (!await scheduler.CheckExists(jobKey)) { - _logger.LogError("Job {jobName} does not exist", jobName); + _logger.LogError("Job {name} does not exist", jobName); return false; } await scheduler.ResumeJob(jobKey); - _logger.LogInformation("Job {jobName} resumed successfully", jobName); + _logger.LogInformation("Job {name} resumed successfully", jobName); return true; } catch (Exception ex) { - _logger.LogError(ex, "Error resuming job {jobName}", jobName); + _logger.LogError(ex, "Error resuming job {name}", jobName); return false; } } @@ -247,7 +247,7 @@ public class JobManagementService : IJobManagementService if (!await scheduler.CheckExists(jobKey)) { - _logger.LogError("Job {jobName} does not exist", jobName); + _logger.LogError("Job {name} does not exist", jobName); return new JobInfo { Name = jobName, Status = "Not Found" }; } @@ -290,7 +290,7 @@ public class JobManagementService : IJobManagementService } catch (Exception ex) { - _logger.LogError(ex, "Error getting job {jobName}", jobName); + _logger.LogError(ex, "Error getting job {name}", jobName); return new JobInfo { Name = jobName, Status = "Error" }; } } @@ -310,7 +310,7 @@ public class JobManagementService : IJobManagementService if (!await scheduler.CheckExists(jobKey)) { - _logger.LogError("Job {jobName} does not exist", jobName); + _logger.LogError("Job {name} does not exist", jobName); return false; } @@ -332,12 +332,12 @@ public class JobManagementService : IJobManagementService await scheduler.ScheduleJob(newTrigger); } - _logger.LogInformation("Job {jobName} schedule updated successfully to {cronExpression}", jobName, cronExpression); + _logger.LogInformation("Job {name} schedule updated successfully to {cronExpression}", jobName, cronExpression); return true; } catch (Exception ex) { - _logger.LogError(ex, "Error updating job {jobName} schedule", jobName); + _logger.LogError(ex, "Error updating job {name} schedule", jobName); return false; } } diff --git a/code/Infrastructure/Verticals/ContentBlocker/ContentBlocker.cs b/code/Infrastructure/Verticals/ContentBlocker/ContentBlocker.cs deleted file mode 100644 index 8f0d35f7..00000000 --- a/code/Infrastructure/Verticals/ContentBlocker/ContentBlocker.cs +++ /dev/null @@ -1,187 +0,0 @@ -// using System.Collections.Concurrent; -// using System.Text.RegularExpressions; -// using Common.Configuration.Arr; -// using Common.Configuration.DownloadClient; -// using Common.Configuration.QueueCleaner; -// using Data.Enums; -// using Data.Models.Arr.Queue; -// using Infrastructure.Configuration; -// using Infrastructure.Helpers; -// using Infrastructure.Services; -// using Infrastructure.Verticals.Arr; -// using Infrastructure.Verticals.Arr.Interfaces; -// using Infrastructure.Verticals.DownloadClient; -// using Infrastructure.Verticals.Jobs; -// using MassTransit; -// using Microsoft.Extensions.Caching.Memory; -// using Microsoft.Extensions.Logging; -// using LogContext = Serilog.Context.LogContext; -// -// namespace Infrastructure.Verticals.ContentBlocker; -// -// public sealed class ContentBlocker : GenericHandler -// { -// private readonly ContentBlockerConfig _config; -// private readonly BlocklistProvider _blocklistProvider; -// private readonly IIgnoredDownloadsService _ignoredDownloadsService; -// -// public ContentBlocker( -// ILogger logger, -// IConfigManager configManager, -// IMemoryCache cache, -// IBus messageBus, -// ArrClientFactory arrClientFactory, -// ArrQueueIterator arrArrQueueIterator, -// BlocklistProvider blocklistProvider, -// IIgnoredDownloadsService ignoredDownloadsService, -// DownloadServiceFactory downloadServiceFactory -// ) : base( -// logger, cache, messageBus, -// arrClientFactory, arrArrQueueIterator, downloadServiceFactory -// ) -// { -// _blocklistProvider = blocklistProvider; -// _ignoredDownloadsService = ignoredDownloadsService; -// -// _config = configManager.GetConfiguration(); -// _downloadClientConfig = configManager.GetConfiguration(); -// _sonarrConfig = configManager.GetConfiguration(); -// _radarrConfig = configManager.GetConfiguration(); -// _lidarrConfig = configManager.GetConfiguration(); -// } -// -// public override async Task ExecuteAsync() -// { -// if (_downloadClientConfig.Clients.Count is 0) -// { -// _logger.LogWarning("No download clients configured"); -// return; -// } -// -// bool blocklistIsConfigured = _config.Sonarr.Enabled && !string.IsNullOrEmpty(_config.Sonarr.Path) || -// _config.Radarr.Enabled && !string.IsNullOrEmpty(_config.Radarr.Path) || -// _config.Lidarr.Enabled && !string.IsNullOrEmpty(_config.Lidarr.Path); -// -// if (!blocklistIsConfigured) -// { -// _logger.LogWarning("no blocklist is configured"); -// return; -// } -// -// await _blocklistProvider.LoadBlocklistsAsync(); -// await base.ExecuteAsync(); -// } -// -// protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType, ArrConfig config) -// { -// IReadOnlyList ignoredDownloads = await _ignoredDownloadsService.GetIgnoredDownloadsAsync(); -// -// using var _ = LogContext.PushProperty(LogProperties.Category, instanceType.ToString()); -// -// IArrClient arrClient = _arrClientFactory.GetClient(instanceType); -// BlocklistType blocklistType = _blocklistProvider.GetBlocklistType(instanceType); -// ConcurrentBag patterns = _blocklistProvider.GetPatterns(instanceType); -// ConcurrentBag regexes = _blocklistProvider.GetRegexes(instanceType); -// -// await _arrArrQueueIterator.Iterate(arrClient, instance, async items => -// { -// var groups = items -// .GroupBy(x => x.DownloadId) -// .ToList(); -// -// foreach (var group in groups) -// { -// if (group.Any(x => !arrClient.IsRecordValid(x))) -// { -// continue; -// } -// -// QueueRecord record = group.First(); -// -// if (record.Protocol is not "torrent") -// { -// continue; -// } -// -// if (string.IsNullOrEmpty(record.DownloadId)) -// { -// _logger.LogDebug("skip | download id is null for {title}", record.Title); -// continue; -// } -// -// if (ignoredDownloads.Contains(record.DownloadId, StringComparer.InvariantCultureIgnoreCase)) -// { -// _logger.LogInformation("skip | {title} | ignored", record.Title); -// continue; -// } -// -// _logger.LogTrace("processing | {name}", record.Title); -// -// string downloadRemovalKey = CacheKeys.DownloadMarkedForRemoval(record.DownloadId, instance.Url); -// -// if (_cache.TryGetValue(downloadRemovalKey, out bool _)) -// { -// _logger.LogDebug("skip | already marked for removal | {title}", record.Title); -// continue; -// } -// -// _logger.LogDebug("searching unwanted files for {title}", record.Title); -// bool found = false; -// -// foreach (var downloadService in _downloadServices) -// { -// try -// { -// BlockFilesResult result = await downloadService -// .BlockUnwantedFilesAsync(record.DownloadId, blocklistType, patterns, regexes, ignoredDownloads); -// -// if (!result.Found) -// { -// continue; -// } -// -// found = true; -// -// if (!result.ShouldRemove) -// { -// break; -// } -// -// _logger.LogDebug("all files are marked as unwanted | {hash}", record.Title); -// -// bool removeFromClient = true; -// -// if (result.IsPrivate && !_config.DeletePrivate) -// { -// removeFromClient = false; -// } -// -// await PublishQueueItemRemoveRequest( -// downloadRemovalKey, -// instanceType, -// instance, -// record, -// group.Count() > 1, -// removeFromClient, -// DeleteReason.AllFilesBlocked -// ); -// -// break; -// } -// catch (Exception ex) -// { -// _logger.LogError( -// ex, -// "Error blocking unwanted files for {hash} with download client", -// record.DownloadId); -// } -// } -// -// if (!found) -// { -// _logger.LogWarning("skip | download not found {title}", record.Title); -// } -// } -// }); -// } -// } \ No newline at end of file diff --git a/code/UI/src/app/core/services/configuration.service.ts b/code/UI/src/app/core/services/configuration.service.ts index 532a6a6f..fe5091f6 100644 --- a/code/UI/src/app/core/services/configuration.service.ts +++ b/code/UI/src/app/core/services/configuration.service.ts @@ -1,11 +1,11 @@ -import { HttpClient } from '@angular/common/http'; -import { Injectable, inject } from '@angular/core'; -import { Observable, catchError, map, throwError } from 'rxjs'; -import { environment } from '../../../environments/environment'; -import { JobSchedule, QueueCleanerConfig, ScheduleUnit } from '../../shared/models/queue-cleaner-config.model'; +import { HttpClient } from "@angular/common/http"; +import { Injectable, inject } from "@angular/core"; +import { Observable, catchError, map, throwError } from "rxjs"; +import { environment } from "../../../environments/environment"; +import { JobSchedule, QueueCleanerConfig, ScheduleUnit } from "../../shared/models/queue-cleaner-config.model"; @Injectable({ - providedIn: 'root' + providedIn: "root", }) export class ConfigurationService { private readonly apiUrl = environment.apiUrl; @@ -15,149 +15,29 @@ export class ConfigurationService { * Get queue cleaner configuration */ getQueueCleanerConfig(): Observable { - return this.http.get(`${this.apiUrl}/api/configuration/queue_cleaner`) - .pipe( - map(response => this.transformQueueCleanerResponse(response)), - catchError(error => { - console.error('Error fetching queue cleaner config:', error); - return throwError(() => new Error('Failed to load queue cleaner configuration')); - }) - ); + return this.http.get(`${this.apiUrl}/api/configuration/queue_cleaner`).pipe( + map((response) => { + response.jobSchedule = this.tryExtractJobScheduleFromCron(response.cronExpression); + return response; + }), + catchError((error) => { + console.error("Error fetching queue cleaner config:", error); + return throwError(() => new Error("Failed to load queue cleaner configuration")); + }) + ); } /** * Update queue cleaner configuration */ - updateQueueCleanerConfig(config: QueueCleanerConfig): Observable { - // Create a copy to avoid modifying the original - const configToSend = this.prepareQueueCleanerConfigForSending({ ...config }); - - return this.http.put(`${this.apiUrl}/api/configuration/queue_cleaner`, configToSend) - .pipe( - catchError(error => { - console.error('Error updating queue cleaner config:', error); - return throwError(() => new Error(error.error?.error || 'Failed to update queue cleaner configuration')); - }) - ); - } - - /** - * Transform the API response to our frontend model - * Convert property names from PascalCase to camelCase - */ - private transformQueueCleanerResponse(response: any): QueueCleanerConfig { - const config: QueueCleanerConfig = { - enabled: response.Enabled, - cronExpression: response.CronExpression, - runSequentially: response.RunSequentially, - ignoredDownloadsPath: response.IgnoredDownloadsPath || '', - - // Create the nested configuration objects - failedImport: { - maxStrikes: response.FailedImportMaxStrikes, - ignorePrivate: response.FailedImportIgnorePrivate, - deletePrivate: response.FailedImportDeletePrivate, - ignorePatterns: response.FailedImportIgnorePatterns || [] - }, - - stalled: { - maxStrikes: response.StalledMaxStrikes, - resetStrikesOnProgress: response.StalledResetStrikesOnProgress, - ignorePrivate: response.StalledIgnorePrivate, - deletePrivate: response.StalledDeletePrivate, - downloadingMetadataMaxStrikes: response.DownloadingMetadataMaxStrikes - }, - - slow: { - maxStrikes: response.SlowMaxStrikes, - resetStrikesOnProgress: response.SlowResetStrikesOnProgress, - ignorePrivate: response.SlowIgnorePrivate, - deletePrivate: response.SlowDeletePrivate, - minSpeed: response.SlowMinSpeed || '', - maxTime: response.SlowMaxTime, - ignoreAboveSize: response.SlowIgnoreAboveSize || '' - }, - - contentBlocker: { - enabled: false, // Default values since they're not in the DTO - ignorePrivate: false, - deletePrivate: false, - sonarrBlocklist: { path: '', type: 'Blacklist' as any }, - radarrBlocklist: { path: '', type: 'Blacklist' as any }, - lidarrBlocklist: { path: '', type: 'Blacklist' as any } - }, - - // Keep legacy flat properties for backward compatibility - failedImportMaxStrikes: response.FailedImportMaxStrikes, - failedImportIgnorePrivate: response.FailedImportIgnorePrivate, - failedImportDeletePrivate: response.FailedImportDeletePrivate, - failedImportIgnorePatterns: response.FailedImportIgnorePatterns || [], - stalledMaxStrikes: response.StalledMaxStrikes, - stalledResetStrikesOnProgress: response.StalledResetStrikesOnProgress, - stalledIgnorePrivate: response.StalledIgnorePrivate, - stalledDeletePrivate: response.StalledDeletePrivate, - downloadingMetadataMaxStrikes: response.DownloadingMetadataMaxStrikes, - slowMaxStrikes: response.SlowMaxStrikes, - slowResetStrikesOnProgress: response.SlowResetStrikesOnProgress, - slowIgnorePrivate: response.SlowIgnorePrivate, - slowDeletePrivate: response.SlowDeletePrivate, - slowMinSpeed: response.SlowMinSpeed || '', - slowMaxTime: response.SlowMaxTime, - slowIgnoreAboveSize: response.SlowIgnoreAboveSize || '', - }; - - // Attempt to extract job schedule from cron expression - // This is just UI sugar, not sent back to API - config.jobSchedule = this.tryExtractJobScheduleFromCron(config.cronExpression); - - return config; - } - - /** - * Prepare configuration object for sending to API - * Convert property names from camelCase to PascalCase - */ - private prepareQueueCleanerConfigForSending(config: QueueCleanerConfig): any { - // If we have a job schedule, update the cron expression - if (config.jobSchedule) { - config.cronExpression = this.convertJobScheduleToCron(config.jobSchedule); - } - - // Remove UI-only properties - const { jobSchedule, ...rest } = config; - - // Convert to PascalCase for backend - // Use nested objects if available, fall back to flat properties if needed - return { - Enabled: rest.enabled, - CronExpression: rest.cronExpression, - RunSequentially: rest.runSequentially, - IgnoredDownloadsPath: rest.ignoredDownloadsPath, - - // Failed Import settings - FailedImportMaxStrikes: rest.failedImport?.maxStrikes ?? rest.failedImportMaxStrikes, - FailedImportIgnorePrivate: rest.failedImport?.ignorePrivate ?? rest.failedImportIgnorePrivate, - FailedImportDeletePrivate: rest.failedImport?.deletePrivate ?? rest.failedImportDeletePrivate, - FailedImportIgnorePatterns: rest.failedImport?.ignorePatterns ?? rest.failedImportIgnorePatterns, - - // Stalled settings - StalledMaxStrikes: rest.stalled?.maxStrikes ?? rest.stalledMaxStrikes, - StalledResetStrikesOnProgress: rest.stalled?.resetStrikesOnProgress ?? rest.stalledResetStrikesOnProgress, - StalledIgnorePrivate: rest.stalled?.ignorePrivate ?? rest.stalledIgnorePrivate, - StalledDeletePrivate: rest.stalled?.deletePrivate ?? rest.stalledDeletePrivate, - - // Downloading Metadata settings - DownloadingMetadataMaxStrikes: rest.stalled?.downloadingMetadataMaxStrikes ?? rest.downloadingMetadataMaxStrikes, - - // Slow Download settings - SlowMaxStrikes: rest.slow?.maxStrikes ?? rest.slowMaxStrikes, - SlowResetStrikesOnProgress: rest.slow?.resetStrikesOnProgress ?? rest.slowResetStrikesOnProgress, - SlowIgnorePrivate: rest.slow?.ignorePrivate ?? rest.slowIgnorePrivate, - SlowDeletePrivate: rest.slow?.deletePrivate ?? rest.slowDeletePrivate, - SlowMinSpeed: rest.slow?.minSpeed ?? rest.slowMinSpeed, - SlowMaxTime: rest.slow?.maxTime ?? rest.slowMaxTime, - SlowIgnoreAboveSize: rest.slow?.ignoreAboveSize ?? rest.slowIgnoreAboveSize, - }; + updateQueueCleanerConfig(config: QueueCleanerConfig): Observable { + config.cronExpression = this.convertJobScheduleToCron(config.jobSchedule!); + return this.http.put(`${this.apiUrl}/api/configuration/queue_cleaner`, config).pipe( + catchError((error) => { + console.error("Error updating queue cleaner config:", error); + return throwError(() => new Error(error.error?.error || "Failed to update queue cleaner configuration")); + }) + ); } /** @@ -170,37 +50,37 @@ export class ConfigurationService { // Minutes: 0 */n * ? * * * // Hours: 0 0 */n ? * * * try { - const parts = cronExpression.split(' '); - + const parts = cronExpression.split(" "); + if (parts.length !== 7) return undefined; - + // Every n seconds - if (parts[0].startsWith('*/') && parts[1] === '*') { + if (parts[0].startsWith("*/") && parts[1] === "*") { const seconds = parseInt(parts[0].substring(2)); if (!isNaN(seconds) && seconds > 0 && seconds < 60) { return { every: seconds, type: ScheduleUnit.Seconds }; } } - + // Every n minutes - if (parts[0] === '0' && parts[1].startsWith('*/')) { + if (parts[0] === "0" && parts[1].startsWith("*/")) { const minutes = parseInt(parts[1].substring(2)); if (!isNaN(minutes) && minutes > 0 && minutes < 60) { return { every: minutes, type: ScheduleUnit.Minutes }; } } - + // Every n hours - if (parts[0] === '0' && parts[1] === '0' && parts[2].startsWith('*/')) { + if (parts[0] === "0" && parts[1] === "0" && parts[2].startsWith("*/")) { const hours = parseInt(parts[2].substring(2)); if (!isNaN(hours) && hours > 0 && hours < 24) { return { every: hours, type: ScheduleUnit.Hours }; } } } catch (e) { - console.warn('Could not parse cron expression:', cronExpression); + console.warn("Could not parse cron expression:", cronExpression); } - + return undefined; } @@ -209,7 +89,7 @@ export class ConfigurationService { */ private convertJobScheduleToCron(schedule: JobSchedule): string { if (!schedule || schedule.every <= 0) { - return '0 0/5 * * * ?'; // Default: every 5 minutes + return "0 0/5 * * * ?"; // Default: every 5 minutes } switch (schedule.type) { @@ -218,13 +98,13 @@ export class ConfigurationService { return `*/${schedule.every} * * ? * * *`; } break; - + case ScheduleUnit.Minutes: if (schedule.every < 60) { return `0 */${schedule.every} * ? * * *`; } break; - + case ScheduleUnit.Hours: if (schedule.every < 24) { return `0 0 */${schedule.every} ? * * *`; @@ -233,6 +113,6 @@ export class ConfigurationService { } // Fallback to default - return '0 0/5 * * * ?'; + return "0 0/5 * * * ?"; } }