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
@@ -5,6 +5,11 @@ using Microsoft.Extensions.Logging;
namespace EmailApi.Data.Services;
/// <summary>
/// Singleton implementation of <see cref="IEmailTemplateService"/> that caches all email templates
/// from the database and refreshes them every 10 minutes.
/// Uses <see cref="IServiceScopeFactory"/> to resolve the scoped repository from a singleton lifetime.
/// </summary>
public sealed class EmailTemplateService : IEmailTemplateService
{
private readonly IServiceScopeFactory _scopeFactory;
@@ -20,6 +25,7 @@ public sealed class EmailTemplateService : IEmailTemplateService
_logger = logger;
}
/// <inheritdoc />
public string Get(string key, string language = "en")
{
EnsureCacheLoaded();
@@ -35,6 +41,7 @@ public sealed class EmailTemplateService : IEmailTemplateService
return key;
}
/// <inheritdoc />
public string Render(string key, string language, params (string Key, string Value)[] placeholders)
{
var template = Get(key, language);
@@ -43,6 +50,7 @@ public sealed class EmailTemplateService : IEmailTemplateService
return template;
}
/// <inheritdoc />
public string? GetOperatorCopy(string key, string language)
{
EnsureCacheLoaded();
@@ -61,6 +69,10 @@ public sealed class EmailTemplateService : IEmailTemplateService
return null;
}
/// <summary>
/// Reloads all templates from the database when the cache TTL has expired.
/// Swaps both caches atomically; logs an error and continues serving the stale cache on failure.
/// </summary>
private void EnsureCacheLoaded()
{
if (DateTime.UtcNow - _loadedAt < CacheTtl) return;
@@ -91,5 +103,6 @@ public sealed class EmailTemplateService : IEmailTemplateService
}
}
/// <summary>Builds the dictionary key used for both caches.</summary>
private static string CacheKey(string key, string language) => $"{key}::{language}";
}
@@ -1,8 +1,38 @@
namespace EmailApi.Data.Services;
/// <summary>
/// Provides access to localised email templates stored in the <c>emailApi.EmailTemplates</c> table.
/// Implementations are expected to cache templates and refresh periodically.
/// </summary>
public interface IEmailTemplateService
{
/// <summary>
/// Returns the template value for the given key and language.
/// Falls back to <c>"en"</c> when the requested language has no entry.
/// Returns the raw key string when no matching template is found.
/// </summary>
/// <param name="key">Template key (e.g. <c>"email.match.subject"</c>).</param>
/// <param name="language">Two-letter language code (e.g. <c>"en"</c>, <c>"ro"</c>).</param>
/// <returns>Template value string.</returns>
string Get(string key, string language = "en");
/// <summary>
/// Retrieves the template and substitutes <c>{{placeholder}}</c> tokens with the provided values.
/// </summary>
/// <param name="key">Template key.</param>
/// <param name="language">Two-letter language code.</param>
/// <param name="placeholders">Named replacement pairs in the form <c>("name", value)</c>.</param>
/// <returns>Rendered template string with all placeholders replaced.</returns>
string Render(string key, string language, params (string Key, string Value)[] placeholders);
/// <summary>
/// Returns the operator copy address for the given template key.
/// Uses the specific row's <c>OperatorCopy</c> value when non-empty; otherwise falls back
/// to the first non-empty <c>OperatorCopy</c> across all cached rows, so future template rows
/// with an empty value automatically inherit the globally configured address.
/// </summary>
/// <param name="key">Template key used to look up the specific row (typically the subject key).</param>
/// <param name="language">Two-letter language code.</param>
/// <returns>Operator copy email address, or <c>null</c> when none is configured.</returns>
string? GetOperatorCopy(string key, string language);
}