Hide + archive stale listings (old jobs, expired shifts)
CI/CD / CI · dotnet build (push) Successful in 37s
CI/CD / Deploy · hamkadr (push) Successful in 48s

- ListingPolicy.JobFreshnessDays=30: public /Jobs and home hide jobs older than the cutoff (shifts already require Date>=today)
- ListingArchiver flips stale Open→Expired: shifts past their date, jobs older than the cutoff. Runs at startup and on every IngestionWorker cycle (independent of ingestion being enabled)
- Verified: backdated job dropped off /Jobs (6→5) and was archived to Expired on the sweep

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-04 09:57:06 +03:30
parent 178e44c4da
commit 6d2ad6f87e
5 changed files with 62 additions and 2 deletions
@@ -28,6 +28,10 @@ public class IngestionWorker : BackgroundService
try
{
using var scope = _scopes.CreateScope();
// Always archive stale listings (independent of ingestion being on).
await scope.ServiceProvider.GetRequiredService<ListingArchiver>().ArchiveStaleAsync(stoppingToken);
var settings = await scope.ServiceProvider
.GetRequiredService<SettingsService>().GetAsync();
@@ -0,0 +1,49 @@
using JobsMedical.Web.Data;
using JobsMedical.Web.Models;
using Microsoft.EntityFrameworkCore;
namespace JobsMedical.Web.Services.Scraping;
/// <summary>
/// Centralizes "what counts as a live listing". Jobs have no end date, so they go stale after a
/// window; shifts expire once their date has passed. Public screens hide anything outside these
/// rules, and <see cref="ListingArchiver"/> flips stale Open rows to Expired so dashboards/DB agree.
/// </summary>
public static class ListingPolicy
{
/// <summary>A job opening older than this (since posting) is treated as stale and hidden.</summary>
public const int JobFreshnessDays = 30;
public static DateTime JobCutoffUtc => DateTime.UtcNow.AddDays(-JobFreshnessDays);
}
/// <summary>Sweeps stale listings into the Expired state (archive). Idempotent and cheap.</summary>
public class ListingArchiver
{
private readonly AppDbContext _db;
private readonly ILogger<ListingArchiver> _log;
public ListingArchiver(AppDbContext db, ILogger<ListingArchiver> log)
{
_db = db;
_log = log;
}
public async Task<int> ArchiveStaleAsync(CancellationToken ct = default)
{
var today = DateOnly.FromDateTime(DateTime.UtcNow);
var jobCutoff = ListingPolicy.JobCutoffUtc;
var expiredShifts = await _db.Shifts
.Where(s => s.Status == ShiftStatus.Open && s.Date < today)
.ExecuteUpdateAsync(u => u.SetProperty(s => s.Status, ShiftStatus.Expired), ct);
var expiredJobs = await _db.JobOpenings
.Where(j => j.Status == ShiftStatus.Open && j.CreatedAt < jobCutoff)
.ExecuteUpdateAsync(u => u.SetProperty(j => j.Status, ShiftStatus.Expired), ct);
if (expiredShifts + expiredJobs > 0)
_log.LogInformation("Archived {Shifts} shifts + {Jobs} jobs as expired", expiredShifts, expiredJobs);
return expiredShifts + expiredJobs;
}
}