feat(infrastructure): implement IEmailSyncService on EmailSyncWorker with rate-limited ForceTriggerSync via IMemoryCache; register as singleton in DI

This commit is contained in:
2026-08-17 16:05:30 +02:00
parent c189a2d6ef
commit 8925428e46
2 changed files with 22 additions and 3 deletions

View File

@@ -13,6 +13,7 @@ using Microsoft.AspNetCore.DataProtection;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace DigitalData.MessagingService.Infrastructure; namespace DigitalData.MessagingService.Infrastructure;
@@ -71,6 +72,13 @@ public static class DependencyInjection
services.AddHostedService<AsyncInitWorker>(); services.AddHostedService<AsyncInitWorker>();
services.AddHostedService<EmailSyncWorker>(); services.AddHostedService<EmailSyncWorker>();
services.AddSingleton<IEmailSyncService>(p =>
{
var hostedServices = p.GetRequiredService<IEnumerable<IHostedService>>();
var emailSyncWorkers = hostedServices.OfType<EmailSyncWorker>();
return emailSyncWorkers.FirstOrDefault() ?? throw new InvalidOperationException("EmailSyncWorker is not registered.");
});
services.AddMemoryCache(); services.AddMemoryCache();
// --- Database (InMemory) --- // --- Database (InMemory) ---

View File

@@ -3,6 +3,7 @@ using DigitalData.MessagingService.Application.Common.Interfaces.Repositories;
using DigitalData.MessagingService.Application.Common.Options; using DigitalData.MessagingService.Application.Common.Options;
using DigitalData.MessagingService.Domain.Entities; using DigitalData.MessagingService.Domain.Entities;
using DigitalData.MessagingService.Domain.Enums; using DigitalData.MessagingService.Domain.Enums;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -10,8 +11,10 @@ using Microsoft.Extensions.Options;
namespace DigitalData.MessagingService.Infrastructure.Services.Background; namespace DigitalData.MessagingService.Infrastructure.Services.Background;
public class EmailSyncWorker(IOptions<EmailAccountsOptions> Options, IServiceProvider Provider, ILogger<EmailSyncWorker> Logger) : BackgroundService public class EmailSyncWorker(IOptions<EmailAccountsOptions> Options, IServiceProvider Provider, ILogger<EmailSyncWorker> Logger, IMemoryCache Cache) : BackgroundService, IEmailSyncService
{ {
private static string ForcedSyncDateCacheKey => $"{nameof(EmailSyncWorker)}_TriggerSync";
private readonly string DefaultFolder = "INBOX"; private readonly string DefaultFolder = "INBOX";
/// <summary> /// <summary>
@@ -26,7 +29,15 @@ public class EmailSyncWorker(IOptions<EmailAccountsOptions> Options, IServicePro
/// Safe to call from any thread or HTTP request at any time. /// Safe to call from any thread or HTTP request at any time.
/// If a sync is already running, the trigger is ignored — the next cycle starts normally. /// If a sync is already running, the trigger is ignored — the next cycle starts normally.
/// </summary> /// </summary>
public void TriggerSync() => _syncTrigger.TrySetResult(true); public DateTime ForceTriggerSync()
{
return Cache.GetOrCreate(ForcedSyncDateCacheKey, e =>
{
e.SetAbsoluteExpiration(TimeSpan.FromSeconds(Options.Value.ForcedSyncIntervalSeconds));
_syncTrigger.TrySetResult(true);
return DateTime.UtcNow;
});
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{ {
@@ -98,7 +109,7 @@ public class EmailSyncWorker(IOptions<EmailAccountsOptions> Options, IServicePro
} }
} }
public async Task UpsertSeedEmailAccount(CancellationToken stoppingToken) private async Task UpsertSeedEmailAccount(CancellationToken stoppingToken)
{ {
await using var scope = Provider.CreateAsyncScope(); await using var scope = Provider.CreateAsyncScope();