Add searched location to job search results email
Build and Push Docker Images Staging / build (push) Successful in 14m42s

Show the candidate's location in the scan summary block of the results email
alongside keywords and providers, for both en and ro templates.

- CvSearchEmailSender.SendResultsAsync accepts location and passes it to BuildScanSummary
- BuildScanSummary passes {{location}} to the template (falls back to '-' when absent)
- CvSearchJobTask passes session.Location to SendResultsAsync
- New migration AddLocationToScanSummaryTemplate updates both language variants of
  email.search-results.scan-summary to include a 'Location / Locație căutată' row

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 15:54:38 +03:00
parent 709c0ac4c3
commit c89df975bd
4 changed files with 159 additions and 5 deletions
@@ -46,6 +46,7 @@ public sealed class CvSearchEmailSender
IReadOnlyList<string> keywords,
IReadOnlyList<string> providerNames,
string language,
string? location,
CancellationToken ct)
{
var operatorCopy = _emailTemplates.GetOperatorCopy("email.search-results.subject", language);
@@ -58,7 +59,7 @@ public sealed class CvSearchEmailSender
if (recipients.Count == 0) return;
var htmlBody = BuildBody(results, keywords, providerNames, language);
var htmlBody = BuildBody(results, keywords, providerNames, language, location);
var subject = _emailTemplates.Render("email.search-results.subject", language,
("count", results.Count.ToString()));
@@ -87,9 +88,9 @@ public sealed class CvSearchEmailSender
/// Returns the empty-results template when no results are present.
/// Prepends a scan summary block showing the keywords and providers used.
/// </summary>
private string BuildBody(IReadOnlyList<JobSearchResultEntity> results, IReadOnlyList<string> keywords, IReadOnlyList<string> providerNames, string language)
private string BuildBody(IReadOnlyList<JobSearchResultEntity> results, IReadOnlyList<string> keywords, IReadOnlyList<string> providerNames, string language, string? location)
{
var scanSummary = BuildScanSummary(keywords, providerNames, language);
var scanSummary = BuildScanSummary(keywords, providerNames, language, location);
if (results.Count == 0)
return scanSummary + _emailTemplates.Get("email.search-results.empty", language);
@@ -121,7 +122,7 @@ public sealed class CvSearchEmailSender
/// Renders the scan summary block via template, passing keyword tags and provider list as data.
/// Keyword tags are built here because they are variable-count inline elements, not structural HTML.
/// </summary>
private string BuildScanSummary(IReadOnlyList<string> keywords, IReadOnlyList<string> providerNames, string language)
private string BuildScanSummary(IReadOnlyList<string> keywords, IReadOnlyList<string> providerNames, string language, string? location)
{
var keywordsHtml = keywords.Count > 0
? string.Join(" ", keywords.Select(k =>
@@ -132,9 +133,12 @@ public sealed class CvSearchEmailSender
? string.Join(", ", providerNames)
: "none";
var locationDisplay = string.IsNullOrWhiteSpace(location) ? "-" : location;
return _emailTemplates.Render("email.search-results.scan-summary", language,
("keywordsHtml", keywordsHtml),
("providers", providers));
("providers", providers),
("location", locationDisplay));
}
/// <summary>
@@ -111,6 +111,7 @@ public sealed class CvSearchJobTask : IJobTask
cvKeywords,
providers.Select(p => p.Name).ToList(),
pending.Language,
pending.Location,
cancellationToken);
_logger.LogInformation("Session {SessionId} done. {Count} results sent.", pending.Id, results.Count);