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:
cesnimda
2026-07-31 16:54:16 +02:00
parent a23c3dfc97
commit ce76046a29
1634 changed files with 6889 additions and 135429 deletions
+29 -3
View File
@@ -1,5 +1,7 @@
using System.Threading.Channels;
using JobTrackerApi.Controllers;
using JobTrackerApi.Data;
using Microsoft.EntityFrameworkCore;
namespace JobTrackerApi.Services;
@@ -50,13 +52,13 @@ public sealed class CvProcessingHostedService : BackgroundService
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await ProcessInterruptedRunsAsync(stoppingToken);
await foreach (var runId in _queue.DequeueAllAsync(stoppingToken))
{
try
{
await using var scope = _scopeFactory.CreateAsyncScope();
var controller = scope.ServiceProvider.GetRequiredService<ProfileCvController>();
await controller.ProcessQueuedRunAsync(runId, stoppingToken);
await ProcessRunAsync(runId, stoppingToken);
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
@@ -68,4 +70,28 @@ public sealed class CvProcessingHostedService : BackgroundService
}
}
}
private async Task ProcessInterruptedRunsAsync(CancellationToken cancellationToken)
{
await using var scope = _scopeFactory.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<JobTrackerContext>();
var interruptedRuns = await db.CvExtractionRuns.IgnoreQueryFilters()
.Where(x => x.Status == "queued" || x.Status == "running")
.Select(x => new { x.Id, x.StartedAtUtc })
.ToListAsync(cancellationToken);
// ponytail: single-instance recovery; use row leasing if multiple workers are ever deployed.
// SQLite cannot ORDER BY DateTimeOffset, so the small interrupted-work set is ordered locally.
foreach (var run in interruptedRuns.OrderBy(x => x.StartedAtUtc))
{
await ProcessRunAsync(run.Id, cancellationToken);
}
}
private async Task ProcessRunAsync(int runId, CancellationToken cancellationToken)
{
await using var scope = _scopeFactory.CreateAsyncScope();
var controller = scope.ServiceProvider.GetRequiredService<ProfileCvController>();
await controller.ProcessQueuedRunAsync(runId, cancellationToken);
}
}