feat: complete release readiness work
- consolidate API ownership and remove dead vendor code - add Stripe billing, learning paths, and public CV hardening - add migration, recovery, security, audit, and browser gates
This commit is contained in:
@@ -315,7 +315,7 @@ public sealed partial class ProfileCvController : ControllerBase
|
||||
[NonAction]
|
||||
public async Task ProcessQueuedRunAsync(int runId, CancellationToken cancellationToken)
|
||||
{
|
||||
var run = await _db.CvExtractionRuns.FirstOrDefaultAsync(x => x.Id == runId, cancellationToken);
|
||||
var run = await _db.CvExtractionRuns.IgnoreQueryFilters().FirstOrDefaultAsync(x => x.Id == runId, cancellationToken);
|
||||
if (run is null) return;
|
||||
var user = await _users.FindByIdAsync(run.OwnerUserId);
|
||||
if (user is null)
|
||||
@@ -367,7 +367,7 @@ public sealed partial class ProfileCvController : ControllerBase
|
||||
}
|
||||
case "reprocess":
|
||||
{
|
||||
var artifact = await _db.CvUploadArtifacts.AsNoTracking().FirstOrDefaultAsync(x => x.Id == run.ArtifactId && x.OwnerUserId == user.Id, cancellationToken);
|
||||
var artifact = await _db.CvUploadArtifacts.IgnoreQueryFilters().AsNoTracking().FirstOrDefaultAsync(x => x.Id == run.ArtifactId && x.OwnerUserId == user.Id, cancellationToken);
|
||||
if (artifact is null) throw new InvalidOperationException("Upload a CV before reprocessing it.");
|
||||
if (string.IsNullOrWhiteSpace(artifact.StoragePath) || !System.IO.File.Exists(artifact.StoragePath))
|
||||
{
|
||||
@@ -416,14 +416,46 @@ public sealed partial class ProfileCvController : ControllerBase
|
||||
|
||||
private async Task PruneExtractionRunsAsync(string ownerUserId, CancellationToken cancellationToken)
|
||||
{
|
||||
var expired = await _db.CvExtractionRuns
|
||||
var expired = await _db.CvExtractionRuns.IgnoreQueryFilters()
|
||||
.Where(x => x.OwnerUserId == ownerUserId && x.Status != "queued" && x.Status != "running")
|
||||
.OrderByDescending(x => x.StartedAtUtc)
|
||||
.Skip(ExtractionRunRetentionCount)
|
||||
.ToListAsync(cancellationToken);
|
||||
if (expired.Count == 0) return;
|
||||
_db.CvExtractionRuns.RemoveRange(expired);
|
||||
if (expired.Count > 0)
|
||||
{
|
||||
_db.CvExtractionRuns.RemoveRange(expired);
|
||||
await _db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var referencedArtifactIds = await _db.CvExtractionRuns.IgnoreQueryFilters()
|
||||
.Where(x => x.OwnerUserId == ownerUserId && x.ArtifactId != null)
|
||||
.Select(x => x.ArtifactId!.Value)
|
||||
.Distinct()
|
||||
.ToListAsync(cancellationToken);
|
||||
var user = await _users.FindByIdAsync(ownerUserId);
|
||||
if (user?.CurrentCvUploadArtifactId is int currentArtifactId)
|
||||
{
|
||||
referencedArtifactIds.Add(currentArtifactId);
|
||||
}
|
||||
|
||||
var orphanedArtifacts = await _db.CvUploadArtifacts.IgnoreQueryFilters()
|
||||
.Where(x => x.OwnerUserId == ownerUserId && !referencedArtifactIds.Contains(x.Id))
|
||||
.ToListAsync(cancellationToken);
|
||||
if (orphanedArtifacts.Count == 0) return;
|
||||
|
||||
_db.CvUploadArtifacts.RemoveRange(orphanedArtifacts);
|
||||
await _db.SaveChangesAsync(cancellationToken);
|
||||
foreach (var artifact in orphanedArtifacts)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(artifact.StoragePath)) System.IO.File.Delete(artifact.StoragePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Could not delete unreferenced CV artifact {ArtifactId} at {Path}", artifact.Id, artifact.StoragePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendRunCompletionEmailAsync(ApplicationUser user, CvExtractionRun run, bool success, CancellationToken cancellationToken)
|
||||
|
||||
Reference in New Issue
Block a user