refactor(api): extract JobApplications DTOs and helpers, fix N+1 aggregation

- Move inline DTOs to JobApplicationDtos.cs, pure static helpers to JobApplicationHelpers.cs
- GetStats aggregates server-side (COUNT/GROUP BY) instead of loading the full table
- Cache RuleSettings via IMemoryCache, keyed per-user (RulesEngine.GetSettings falls back
  to per-user UserRuleSettings overrides, so a single global cache key would leak settings
  across users)
- Add missing AsNoTracking() to read-only GET endpoints (GetAll, GetById, GetBoard,
  GetReminders, GetStatusSuggestion, GetMatchScore, GetCandidateFit, GetFocusPlan,
  GetInterviewPrep, GetReadiness)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-07-12 20:08:59 +02:00
parent bd07876a41
commit ea6c3650f3
10 changed files with 936 additions and 875 deletions
@@ -39,7 +39,7 @@ public sealed class JobApplicationsApplicationPackageTests
await db.SaveChangesAsync();
var controller = CreateController(db, Mock.Of<ISummarizerService>(), "user-1");
var result = await controller.SaveApplicationDrafts(job.Id, new JobApplicationsController.SaveApplicationDraftsRequest(null, "Updated notes block", null), CancellationToken.None);
var result = await controller.SaveApplicationDrafts(job.Id, new SaveApplicationDraftsRequest(null, "Updated notes block", null), CancellationToken.None);
Assert.IsType<NoContentResult>(result);
var saved = await db.JobApplications.FirstAsync();
@@ -135,7 +135,7 @@ public sealed class JobApplicationsApplicationPackageTests
var result = await controller.GenerateApplicationPackage(job.Id, null, null, null, CancellationToken.None);
var ok = Assert.IsType<OkObjectResult>(result.Result);
var payload = Assert.IsType<JobApplicationsController.GenerateApplicationPackageDto>(ok.Value);
var payload = Assert.IsType<GenerateApplicationPackageDto>(ok.Value);
Assert.Contains("Tailored CV", payload.TailoredCvText);
Assert.Equal("Cover letter tailored with recruiter context and imported correspondence.", payload.CoverLetterDraft);
@@ -261,7 +261,7 @@ public sealed class JobApplicationsApplicationPackageTests
var result = await controller.GetTailoredCvDraft(job.Id, CancellationToken.None);
var ok = Assert.IsType<OkObjectResult>(result.Result);
var payload = Assert.IsType<JobApplicationsController.TailoredCvDraftDto>(ok.Value);
var payload = Assert.IsType<TailoredCvDraftDto>(ok.Value);
Assert.True(payload.IsLegacyFallback);
Assert.Equal("legacy-text", payload.TemplateId);
Assert.Contains("Existing tailored CV text", payload.RenderedText);
@@ -337,14 +337,14 @@ public sealed class JobApplicationsApplicationPackageTests
var controller = CreateController(db, summarizer.Object, "user-1");
var generateResult = await controller.GenerateTailoredCvDraft(job.Id, "ats", CancellationToken.None);
var generateOk = Assert.IsType<OkObjectResult>(generateResult.Result);
var generated = Assert.IsType<JobApplicationsController.TailoredCvDraftDto>(generateOk.Value);
var generated = Assert.IsType<TailoredCvDraftDto>(generateOk.Value);
Assert.False(generated.IsLegacyFallback);
Assert.Equal(7, generated.CanonicalProfileVersion);
Assert.Equal("Senior Backend Engineer", generated.Headline);
Assert.Contains("Led backend API delivery.", generated.RenderedText);
var saveResult = await controller.SaveTailoredCvDraft(job.Id, new JobApplicationsController.SaveTailoredCvDraftRequest(
var saveResult = await controller.SaveTailoredCvDraft(job.Id, new SaveTailoredCvDraftRequest(
generated.TemplateId,
"Principal Backend Engineer",
new List<string> { "Own backend delivery for critical APIs." },
@@ -395,7 +395,7 @@ public sealed class JobApplicationsApplicationPackageTests
var renderer = new TestCvTemplateRenderer();
var exporter = new TestCvPdfExporter();
var controller = CreateController(db, Mock.Of<ISummarizerService>(), "user-1", renderer, exporter);
var request = new JobApplicationsController.TailoredCvRenderRequest(
var request = new TailoredCvRenderRequest(
"ats-minimal",
"Backend Engineer",
new List<string> { "Built APIs" },
@@ -409,7 +409,7 @@ public sealed class JobApplicationsApplicationPackageTests
var previewResult = await controller.PreviewTailoredCv(job.Id, request, CancellationToken.None);
var ok = Assert.IsType<OkObjectResult>(previewResult.Result);
var preview = Assert.IsType<JobApplicationsController.TailoredCvPreviewDto>(ok.Value);
var preview = Assert.IsType<TailoredCvPreviewDto>(ok.Value);
Assert.Equal("ats-minimal", preview.TemplateId);
Assert.Equal("preview.pdf", preview.SuggestedFileName);
Assert.Equal("data:image/png;base64,abc123", renderer.LastPhotoDataUrl);