using Fitness_core_BE.Models; using Microsoft.EntityFrameworkCore; namespace Fitness_core_BE.Services { public sealed record JobRunRecord( string JobName, Guid ClientId, DateTime RunAtUtc, string Status, int SubscriptionsRenewed, string? ErrorMessage); // Persisted in CentralDbContext.JobRunLogs. Callers pass in whichever CentralDbContext they // already have open -- background jobs get theirs from TenantSchemaIterator (central is one // fixed database regardless of tenant, so any instance points at the same schema), the admin // controller gets a normal DI-injected one since it runs inside a real HTTP request. public interface IJobRunHistory { Task RecordAsync(CentralDbContext central, JobRunRecord record, CancellationToken ct = default); Task> GetRecentAsync(CentralDbContext central, string jobName, int take, CancellationToken ct = default); } public sealed class DbJobRunHistory : IJobRunHistory { public async Task RecordAsync(CentralDbContext central, JobRunRecord record, CancellationToken ct = default) { central.JobRunLogs.Add(new JobRunLog { JobRunLogId = Guid.NewGuid(), JobName = record.JobName, FKClientId = record.ClientId, RunAtUtc = record.RunAtUtc, Status = record.Status, SubscriptionsRenewed = record.SubscriptionsRenewed, ErrorMessage = record.ErrorMessage }); await central.SaveChangesAsync(ct); } public async Task> GetRecentAsync( CentralDbContext central, string jobName, int take, CancellationToken ct = default) { return await central.JobRunLogs .AsNoTracking() .Where(r => r.JobName == jobName) .OrderByDescending(r => r.RunAtUtc) .Take(take) .Select(r => new JobRunRecord(r.JobName, r.FKClientId, r.RunAtUtc, r.Status, r.SubscriptionsRenewed, r.ErrorMessage)) .ToListAsync(ct); } } }