using Microsoft.Extensions.Options; using SeaHaven.Services.Implementation; using SeaHaven.Services.Interfaces; using Api.SeaHavenIndustries.Options; using Api.SeaHavenIndustries.Observability; using Sentry; namespace Api.SeaHavenIndustries.HostedServices { public class PastDueCacheHostedService : BackgroundService { private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; private readonly PastDueCacheJobOptions _options; private readonly WorkOrderJobRunState _runState; private readonly IHub _sentryHub; private DateOnly? _lastRunDate; public PastDueCacheHostedService( IServiceScopeFactory scopeFactory, IOptions options, WorkOrderJobRunState runState, ILogger logger, IHub sentryHub) { _scopeFactory = scopeFactory; _logger = logger; _options = options.Value.PastDueCache; _runState = runState; _sentryHub = sentryHub; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { if (_options.Enabled && ShouldRunNow()) { var today = DateOnly.FromDateTime(DateTime.UtcNow); if (_lastRunDate != today) { if (await RunJobAsync(stoppingToken)) _lastRunDate = today; } } await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); } } private async Task RunJobAsync(CancellationToken stoppingToken) { using var transaction = SentryObservability.BeginBackgroundTransaction( _sentryHub, "workorders.past-due-cache-job", $"{nameof(PastDueCacheHostedService)}.{nameof(RunJobAsync)}"); try { using var scope = _scopeFactory.CreateScope(); var service = scope.ServiceProvider.GetRequiredService(); await service.RefreshAsync(stoppingToken); _runState.LastPastDueCacheRunUtc = DateTime.UtcNow; _runState.LastPastDueCacheError = null; transaction.FinishOk(); return true; } catch (OperationCanceledException) { transaction.FinishCancelled(); throw; } catch (Exception ex) when (ex is not OperationCanceledException) { _runState.LastPastDueCacheError = ex.Message; _logger.LogError(ex, "PastDue cache hosted job failed."); transaction.FinishError(ex); return false; } } private bool ShouldRunNow() { var now = DateTime.UtcNow; if (!TimeOnly.TryParse(_options.RunAtUtc, out var runAt)) runAt = new TimeOnly(0, 10); return TimeOnly.FromDateTime(now) >= runAt; } } }