70 lines
2.5 KiB
C#
70 lines
2.5 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using JobTrackerApi.Data;
|
|
|
|
namespace JobTrackerApi.Services
|
|
{
|
|
// Periodically applies "auto ghost" transitions.
|
|
public sealed class RulesHostedService : BackgroundService
|
|
{
|
|
private readonly IServiceProvider _services;
|
|
private readonly IStartupReadiness _startupReadiness;
|
|
|
|
public RulesHostedService(IServiceProvider services, IStartupReadiness startupReadiness)
|
|
{
|
|
_services = services;
|
|
_startupReadiness = startupReadiness;
|
|
}
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
{
|
|
await _startupReadiness.WaitUntilReadyAsync(stoppingToken);
|
|
// Small initial delay to let app start.
|
|
await Task.Delay(TimeSpan.FromSeconds(2), stoppingToken);
|
|
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
using var scope = _services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<JobTrackerContext>();
|
|
|
|
var settings = await RulesEngine.GetSettings(db, stoppingToken);
|
|
var now = DateTime.Now;
|
|
|
|
// Get last correspondence per job (single query).
|
|
var lastMsg = await db.Correspondences
|
|
.GroupBy(c => c.JobApplicationId)
|
|
.Select(g => new { JobApplicationId = g.Key, Last = g.Max(x => x.Date) })
|
|
.ToDictionaryAsync(x => x.JobApplicationId, x => (DateTime?)x.Last, stoppingToken);
|
|
|
|
var jobs = await db.JobApplications
|
|
.Where(j => !j.IsDeleted && j.Status != "Ghosted")
|
|
.ToListAsync(stoppingToken);
|
|
|
|
var changed = 0;
|
|
foreach (var j in jobs)
|
|
{
|
|
lastMsg.TryGetValue(j.Id, out var lm);
|
|
var d = RulesEngine.Evaluate(settings, j, now, lm);
|
|
if (d.ShouldGhost)
|
|
{
|
|
j.Status = "Ghosted";
|
|
changed++;
|
|
}
|
|
}
|
|
|
|
if (changed > 0)
|
|
await db.SaveChangesAsync(stoppingToken);
|
|
}
|
|
catch
|
|
{
|
|
// Best-effort background job; swallow errors.
|
|
}
|
|
|
|
await Task.Delay(TimeSpan.FromMinutes(30), stoppingToken);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|