using Distribution.Infra.UoW;
namespace Distribution.Features.Notifications;
///
/// Daily retention purge: deletes read inbox rows older than Notifications:RetentionDays
/// (default 90), then any notification left with no recipients.
///
public class NotificationCleanupJob(
IServiceScopeFactory scopes,
IConfiguration config,
ILogger logger) : BackgroundService
{
private const int BatchSize = 500;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Let the app finish starting before the first run.
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
while (!stoppingToken.IsCancellationRequested)
{
try { await RunOnceAsync(stoppingToken); }
catch (OperationCanceledException) { break; }
catch (Exception ex) { logger.LogError(ex, "Notification cleanup failed."); }
await Task.Delay(TimeSpan.FromHours(24), stoppingToken);
}
}
private async Task RunOnceAsync(CancellationToken ct)
{
var retentionDays = config.GetValue("Notifications:RetentionDays", 90);
var cutoff = DateTime.Now.AddDays(-retentionDays);
using var scope = scopes.CreateScope();
var repo = scope.ServiceProvider.GetRequiredService();
var uow = scope.ServiceProvider.GetRequiredService();
var removed = 0;
while (true)
{
var expired = await repo.GetExpiredReadRecipientsAsync(cutoff, BatchSize);
if (expired.Count == 0) break;
repo.RemoveRecipients(expired);
await uow.CommitAsync(ct);
removed += expired.Count;
}
while (true)
{
var orphans = await repo.GetOrphanNotificationsAsync(BatchSize);
if (orphans.Count == 0) break;
repo.RemoveNotifications(orphans);
await uow.CommitAsync(ct);
}
if (removed > 0)
logger.LogInformation("Notification cleanup removed {Count} read inbox rows older than {Days} days.", removed, retentionDays);
}
}