60 lines
3.6 KiB
C#
60 lines
3.6 KiB
C#
using JobTrackerApi.Data;
|
|
using JobTrackerApi.Models;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace JobTrackerApi.Services;
|
|
|
|
public static class OperationEventLog
|
|
{
|
|
public const int RetentionDays = 30;
|
|
public static string SafeStatus(string? value) => value is
|
|
OperationStatuses.Queued or OperationStatuses.Running or OperationStatuses.WaitingForRetry or OperationStatuses.WaitingForExternalFallback
|
|
or OperationStatuses.Succeeded or OperationStatuses.Failed or OperationStatuses.Cancelled ? value : "unknown";
|
|
|
|
public static string? SafeStage(string? value) => value is "cv_validating_file" or "cv_extracting" or "cv_identifying_sections"
|
|
or "cv_analysing" or "cv_validating_details" or "cv_preparing_review" or "cv_review_ready" ? value : null;
|
|
|
|
public static string? SafeFailure(string? value) => value is null ? null : value is
|
|
"timeout" or "lease_expired" or "deadline_exceeded" or "entitlement_changed" or "document_unavailable"
|
|
or "sidecar_unavailable" or "upstream_unavailable" or "invalid_response" or "circuit_open" or "rate_limited"
|
|
or "permission_denied" or "cancelled" or "upload_rejected" or "parser_failed" or "malformed_output" or "provider_unavailable"
|
|
? value : "processing_failed";
|
|
|
|
public static UserOperationEvent Create(UserOperation operation, OperationEventKind kind, DateTime now,
|
|
string? status = null, string? stage = null, string? failure = null, int? attempt = null) => new()
|
|
{
|
|
OperationId = operation.Id, OwnerUserId = operation.OwnerUserId, AtUtc = now, Kind = kind.ToString(),
|
|
Status = SafeStatus(status ?? operation.Status), Stage = SafeStage(stage ?? operation.ProgressStage),
|
|
Attempt = Math.Clamp(attempt ?? operation.AttemptCount, 0, 10), FailureCode = SafeFailure(failure),
|
|
};
|
|
|
|
public static async Task AppendAsync(JobTrackerContext db, UserOperation operation, OperationEventKind kind, DateTime now,
|
|
CancellationToken token, string? status = null, string? stage = null, string? failure = null, int? attempt = null)
|
|
{
|
|
var item = Create(operation, kind, now, status, stage, failure, attempt);
|
|
if (!db.Database.IsRelational())
|
|
{
|
|
db.UserOperationEvents.Add(item);
|
|
await db.SaveChangesAsync(token);
|
|
return;
|
|
}
|
|
if (db.Database.CurrentTransaction is null) throw new InvalidOperationException("Operation history requires the state transition transaction.");
|
|
// Parameterized insert avoids flushing unrelated entities tracked by a caller.
|
|
await db.Database.ExecuteSqlInterpolatedAsync($"INSERT INTO `UserOperationEvents` (`OperationId`,`OwnerUserId`,`AtUtc`,`Kind`,`Status`,`Stage`,`Attempt`,`FailureCode`) VALUES ({item.OperationId},{item.OwnerUserId},{item.AtUtc},{item.Kind},{item.Status},{item.Stage},{item.Attempt},{item.FailureCode})", token);
|
|
}
|
|
|
|
public static async Task<int> PruneAsync(JobTrackerContext db, DateTime now, CancellationToken token)
|
|
{
|
|
var cutoff = now.AddDays(-RetentionDays);
|
|
var ids = await db.UserOperationEvents.IgnoreQueryFilters().Where(item => item.AtUtc < cutoff)
|
|
.OrderBy(item => item.Id).Select(item => item.Id).Take(5000).ToListAsync(token);
|
|
if (!db.Database.IsRelational())
|
|
{
|
|
var items = await db.UserOperationEvents.IgnoreQueryFilters().Where(item => ids.Contains(item.Id)).ToListAsync(token);
|
|
db.UserOperationEvents.RemoveRange(items);
|
|
return await db.SaveChangesAsync(token);
|
|
}
|
|
return await db.UserOperationEvents.IgnoreQueryFilters().Where(item => ids.Contains(item.Id)).ExecuteDeleteAsync(token);
|
|
}
|
|
}
|