Add XML doc to all service interfaces and implementations (#26)

- Update CLAUDE.md: replace incorrect 'no XML doc on internal code' rule
  with the correct convention (XML doc on all public methods and
  non-trivial private/protected helpers)
- Restore /// <summary> on FileDownloadController private helpers
  (HandleRangeRequest, StreamRangeAsync)
- Add full XML doc to all service contracts:
  ICaptchaVerifier, IEmailSender, ICvMatcherService, IJobTextExtractor,
  IJobTokenService, IDocumentClassifier, IRagService, ITextChunker,
  ITextExtractor, IEmailTemplateService, ITemplateService
- Add /// <summary> and /// <inheritdoc /> to all concrete service classes
  and their methods: RecaptchaVerifier, EmailApiEmailSender,
  SmtpEmailDispatcher, CvMatcherService, JobTextExtractor, JobTokenService,
  RagService, DocumentClassifier, TextChunker, TextExtractor,
  HtmlJobSearcher, CvSearchEmailSender, CvSearchJobTask,
  EmailTemplateService, DbTemplateService

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 09:17:42 +03:00
parent 4ee4a59b5e
commit 16bb195cb5
28 changed files with 436 additions and 6 deletions
@@ -14,6 +14,10 @@ using Microsoft.Extensions.Options;
namespace CvSearchJob.Tasks;
/// <summary>
/// Background job task that processes pending job search sessions: scrapes providers,
/// scores each URL against the CV via the matcher API, persists results, and sends the results email.
/// </summary>
public sealed class CvSearchJobTask : IJobTask
{
private readonly IServiceScopeFactory _scopeFactory;
@@ -41,6 +45,11 @@ public sealed class CvSearchJobTask : IJobTask
_logger = logger;
}
/// <summary>
/// Called by the scheduler on each tick. Resets orphaned sessions, picks the oldest pending session,
/// runs the full search pipeline, and sends the results email.
/// Does nothing when <c>JobSearch:Enabled</c> is <c>false</c>.
/// </summary>
public async Task ExecuteAsync(IConfiguration parametersSection, CancellationToken cancellationToken)
{
if (!_settings.Enabled) return;
@@ -92,6 +101,10 @@ public sealed class CvSearchJobTask : IJobTask
}
}
/// <summary>
/// Runs the full search pipeline for a session: scrapes all providers, deduplicates URLs,
/// scores each candidate via the matcher API, and persists results that meet the minimum score threshold.
/// </summary>
private async Task<List<JobSearchResultEntity>> RunSearchAsync(
JobSearchSessionEntity session,
CvSearchDbContext db,
@@ -163,6 +176,10 @@ public sealed class CvSearchJobTask : IJobTask
return results;
}
/// <summary>
/// Deserialises the provider configuration snapshot stored on the session.
/// Falls back to the current live config when the snapshot is absent or unparseable.
/// </summary>
private List<JobProviderConfig> GetProviders(string? providerConfigJson)
{
if (string.IsNullOrWhiteSpace(providerConfigJson)) return _settings.Providers.Where(p => p.Enabled).ToList();
@@ -178,6 +195,10 @@ public sealed class CvSearchJobTask : IJobTask
}
}
/// <summary>
/// Infers the provider name from the job URL by matching against each provider's <c>JobLinkContains</c> pattern.
/// Falls back to the URL hostname when no provider matches.
/// </summary>
private static string GuessProvider(string url, List<JobProviderConfig> providers)
{
foreach (var p in providers)
@@ -190,6 +211,9 @@ public sealed class CvSearchJobTask : IJobTask
return Uri.TryCreate(url, UriKind.Absolute, out var uri) ? uri.Host : "unknown";
}
/// <summary>
/// Constructs the CV PDF filename from the document ID.
/// </summary>
private static string BuildCvFileName(string cvDocumentId)
{
// Strip non-alphanumeric characters so the filename is safe for all OS/email clients.