diff --git a/EnvelopeGenerator.WorkerService/Configuration/WorkerSettings.cs b/EnvelopeGenerator.WorkerService/Configuration/WorkerSettings.cs
new file mode 100644
index 00000000..dc4d2c91
--- /dev/null
+++ b/EnvelopeGenerator.WorkerService/Configuration/WorkerSettings.cs
@@ -0,0 +1,14 @@
+using EnvelopeGenerator.Jobs.FinalizeDocument;
+
+namespace EnvelopeGenerator.WorkerService.Configuration;
+
+public sealed class WorkerSettings
+{
+ public string ConnectionString { get; set; } = string.Empty;
+
+ public bool Debug { get; set; }
+
+ public int IntervalMinutes { get; set; } = 1;
+
+ public PDFBurnerParams PdfBurner { get; set; } = new();
+}
diff --git a/EnvelopeGenerator.WorkerService/EnvelopeGenerator.WorkerService.csproj b/EnvelopeGenerator.WorkerService/EnvelopeGenerator.WorkerService.csproj
index 868eb4a9..80e64767 100644
--- a/EnvelopeGenerator.WorkerService/EnvelopeGenerator.WorkerService.csproj
+++ b/EnvelopeGenerator.WorkerService/EnvelopeGenerator.WorkerService.csproj
@@ -9,5 +9,10 @@
+
+
+
+
+
diff --git a/EnvelopeGenerator.WorkerService/Program.cs b/EnvelopeGenerator.WorkerService/Program.cs
index b7adaaf1..03e8e055 100644
--- a/EnvelopeGenerator.WorkerService/Program.cs
+++ b/EnvelopeGenerator.WorkerService/Program.cs
@@ -1,6 +1,66 @@
+using EnvelopeGenerator.Domain.Constants;
+using EnvelopeGenerator.Jobs.APIBackendJobs;
+using EnvelopeGenerator.Jobs.FinalizeDocument;
using EnvelopeGenerator.WorkerService;
+using EnvelopeGenerator.WorkerService.Configuration;
+using EnvelopeGenerator.WorkerService.Services;
+using Quartz;
var builder = Host.CreateApplicationBuilder(args);
+
+builder.Services.Configure(builder.Configuration.GetSection("WorkerSettings"));
+
+builder.Services.AddSingleton();
+builder.Services.AddSingleton(provider =>
+{
+ var settings = provider.GetRequiredService>().Value;
+ var logger = provider.GetRequiredService>();
+ return new PDFBurner(logger, settings.PdfBurner);
+});
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+
+builder.Services.AddQuartz(q =>
+{
+ q.UseMicrosoftDependencyInjectionJobFactory();
+ q.UseDefaultThreadPool(tp => tp.MaxConcurrency = 5);
+
+ var settings = new WorkerSettings();
+ builder.Configuration.GetSection("WorkerSettings").Bind(settings);
+ var intervalMinutes = Math.Max(1, settings.IntervalMinutes);
+
+ var finalizeJobKey = new JobKey("FinalizeDocumentJob");
+ q.AddJob(opts => opts
+ .WithIdentity(finalizeJobKey)
+ .UsingJobData(Value.DATABASE, settings.ConnectionString));
+
+ q.AddTrigger(opts => opts
+ .ForJob(finalizeJobKey)
+ .WithIdentity("FinalizeDocumentJob-trigger")
+ .StartNow()
+ .WithSimpleSchedule(x => x
+ .WithIntervalInMinutes(intervalMinutes)
+ .RepeatForever()));
+
+ var apiJobKey = new JobKey("APIEnvelopeJob");
+ q.AddJob(opts => opts
+ .WithIdentity(apiJobKey)
+ .UsingJobData(Value.DATABASE, settings.ConnectionString));
+
+ q.AddTrigger(opts => opts
+ .ForJob(apiJobKey)
+ .WithIdentity("APIEnvelopeJob-trigger")
+ .StartNow()
+ .WithSimpleSchedule(x => x
+ .WithIntervalInMinutes(intervalMinutes)
+ .RepeatForever()));
+});
+
+builder.Services.AddQuartzHostedService(options =>
+{
+ options.WaitForJobsToComplete = true;
+});
+
builder.Services.AddHostedService();
var host = builder.Build();
diff --git a/EnvelopeGenerator.WorkerService/Services/TempFileManager.cs b/EnvelopeGenerator.WorkerService/Services/TempFileManager.cs
new file mode 100644
index 00000000..f98e1c09
--- /dev/null
+++ b/EnvelopeGenerator.WorkerService/Services/TempFileManager.cs
@@ -0,0 +1,74 @@
+using System.IO;
+using Microsoft.Extensions.Logging;
+
+namespace EnvelopeGenerator.WorkerService.Services;
+
+public sealed class TempFileManager
+{
+ private readonly ILogger _logger;
+
+ public TempFileManager(ILogger logger)
+ {
+ _logger = logger;
+ TempPath = Path.Combine(Path.GetTempPath(), "EnvelopeGenerator");
+ }
+
+ public string TempPath { get; }
+
+ public Task CreateAsync(CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ if (!Directory.Exists(TempPath))
+ {
+ Directory.CreateDirectory(TempPath);
+ _logger.LogDebug("Created temp folder {TempPath}", TempPath);
+ }
+ else
+ {
+ CleanUpFiles();
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to create temp folder {TempPath}", TempPath);
+ throw;
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public Task CleanupAsync(CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ if (Directory.Exists(TempPath))
+ {
+ _logger.LogDebug("Deleting temp folder {TempPath}", TempPath);
+ Directory.Delete(TempPath, recursive: true);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to clean up temp folder {TempPath}", TempPath);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ private void CleanUpFiles()
+ {
+ foreach (var file in Directory.GetFiles(TempPath))
+ {
+ try
+ {
+ _logger.LogDebug("Deleting temp file {File}", file);
+ File.Delete(file);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Failed to delete temp file {File}", file);
+ }
+ }
+ }
+}
diff --git a/EnvelopeGenerator.WorkerService/Worker.cs b/EnvelopeGenerator.WorkerService/Worker.cs
index 9e5f2f76..d88c3e63 100644
--- a/EnvelopeGenerator.WorkerService/Worker.cs
+++ b/EnvelopeGenerator.WorkerService/Worker.cs
@@ -1,24 +1,71 @@
-namespace EnvelopeGenerator.WorkerService
+using EnvelopeGenerator.WorkerService.Configuration;
+using EnvelopeGenerator.WorkerService.Services;
+using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.Options;
+
+namespace EnvelopeGenerator.WorkerService;
+
+public class Worker : BackgroundService
{
- public class Worker : BackgroundService
+ private readonly ILogger _logger;
+ private readonly WorkerSettings _settings;
+ private readonly TempFileManager _tempFiles;
+
+ public Worker(
+ ILogger logger,
+ IOptions settings,
+ TempFileManager tempFiles)
{
- private readonly ILogger _logger;
+ _logger = logger;
+ _settings = settings.Value;
+ _tempFiles = tempFiles;
+ }
- public Worker(ILogger logger)
+ public override async Task StartAsync(CancellationToken cancellationToken)
+ {
+ _logger.LogInformation("Starting EnvelopeGenerator worker...");
+ _logger.LogInformation("Debug mode: {Debug}", _settings.Debug);
+
+ ValidateConfiguration();
+ await EnsureDatabaseConnectionAsync(cancellationToken);
+ await _tempFiles.CreateAsync(cancellationToken);
+
+ await base.StartAsync(cancellationToken);
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ _logger.LogInformation("EnvelopeGenerator worker is running. Jobs are scheduled every {Interval} minute(s).", Math.Max(1, _settings.IntervalMinutes));
+ await Task.Delay(Timeout.Infinite, stoppingToken);
+ }
+
+ public override async Task StopAsync(CancellationToken cancellationToken)
+ {
+ _logger.LogInformation("Stopping EnvelopeGenerator worker...");
+ await _tempFiles.CleanupAsync(cancellationToken);
+ await base.StopAsync(cancellationToken);
+ }
+
+ private void ValidateConfiguration()
+ {
+ if (string.IsNullOrWhiteSpace(_settings.ConnectionString))
{
- _logger = logger;
+ throw new InvalidOperationException("Connection string cannot be empty. Configure 'WorkerSettings:ConnectionString'.");
}
+ }
- protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ private async Task EnsureDatabaseConnectionAsync(CancellationToken cancellationToken)
+ {
+ try
{
- while (!stoppingToken.IsCancellationRequested)
- {
- if (_logger.IsEnabled(LogLevel.Information))
- {
- _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
- }
- await Task.Delay(1000, stoppingToken);
- }
+ await using var connection = new SqlConnection(_settings.ConnectionString);
+ await connection.OpenAsync(cancellationToken);
+ _logger.LogInformation("Database connection established successfully.");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Database connection could not be established.");
+ throw;
}
}
}
diff --git a/EnvelopeGenerator.WorkerService/appsettings.Development.json b/EnvelopeGenerator.WorkerService/appsettings.Development.json
index b2dcdb67..9e3a0d90 100644
--- a/EnvelopeGenerator.WorkerService/appsettings.Development.json
+++ b/EnvelopeGenerator.WorkerService/appsettings.Development.json
@@ -1,8 +1,30 @@
{
"Logging": {
"LogLevel": {
- "Default": "Information",
+ "Default": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
+ },
+ "WorkerSettings": {
+ "ConnectionString": "",
+ "Debug": true,
+ "IntervalMinutes": 1,
+ "PdfBurner": {
+ "IgnoredLabels": [
+ "Date",
+ "Datum",
+ "ZIP",
+ "PLZ",
+ "Place",
+ "Ort",
+ "Position",
+ "Stellung"
+ ],
+ "TopMargin": 0.1,
+ "YOffset": -0.3,
+ "FontName": "Arial",
+ "FontSize": 8,
+ "FontStyle": "Italic"
+ }
}
}
diff --git a/EnvelopeGenerator.WorkerService/appsettings.json b/EnvelopeGenerator.WorkerService/appsettings.json
index b2dcdb67..b5f3f582 100644
--- a/EnvelopeGenerator.WorkerService/appsettings.json
+++ b/EnvelopeGenerator.WorkerService/appsettings.json
@@ -4,5 +4,18 @@
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
+ },
+ "WorkerSettings": {
+ "ConnectionString": "",
+ "Debug": false,
+ "IntervalMinutes": 1,
+ "PdfBurner": {
+ "IgnoredLabels": ["Date", "Datum", "ZIP", "PLZ", "Place", "Ort", "Position", "Stellung"],
+ "TopMargin": 0.1,
+ "YOffset": -0.3,
+ "FontName": "Arial",
+ "FontSize": 8,
+ "FontStyle": "Italic"
+ }
}
}