📄 src/Api/CleanupJob.cs
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Quartz;
using Slopper.Domain;

namespace Slopper.Api;

[DisallowConcurrentExecution]
public sealed class CleanupJob(ILogger<CleanupJob> logger, Cleaner cleaner) : IJob
{
    public static JobKey Key { get; } = new(nameof(CleanupJob));

    public async Task Execute(IJobExecutionContext context)
    {
        logger.LogDebug("Running cleanup job");
        try
        {
            await cleaner.Cleanup(context.CancellationToken);
        }
        catch (Exception ex) when (!context.CancellationToken.IsCancellationRequested)
        {
            throw new JobExecutionException(ex, refireImmediately: false);
        }
    }
}

public static class CleanupJobExtensions
{
    extension(IServiceCollectionQuartzConfigurator quartz)
    {
        public IServiceCollectionQuartzConfigurator AddCleanupJob() =>
            quartz
                .AddJob<CleanupJob>(CleanupJob.Key, options => options.StoreDurably())
                .AddTrigger(options => options.ForJob(CleanupJob.Key).WithCronSchedule("H H * * * ?"));
    }
}