diff --git a/Apis/email-data/Migrations/20260608125339_AddLocationToScanSummaryTemplate.Designer.cs b/Apis/email-data/Migrations/20260608125339_AddLocationToScanSummaryTemplate.Designer.cs new file mode 100644 index 0000000..587918e --- /dev/null +++ b/Apis/email-data/Migrations/20260608125339_AddLocationToScanSummaryTemplate.Designer.cs @@ -0,0 +1,69 @@ +// +using System; +using Email.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Email.Data.Migrations +{ + [DbContext(typeof(EmailDbContext))] + [Migration("20260608125339_AddLocationToScanSummaryTemplate")] + partial class AddLocationToScanSummaryTemplate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("email") + .HasAnnotation("ProductVersion", "10.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Email.Data.Entities.EmailTemplateEntity", b => + { + b.Property("Key") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Language") + .HasMaxLength(8) + .HasColumnType("nvarchar(8)"); + + b.Property("Description") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasDefaultValue(""); + + b.Property("OperatorCopy") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasDefaultValue(""); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Key", "Language"); + + b.ToTable("Templates", "email"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Apis/email-data/Migrations/20260608125339_AddLocationToScanSummaryTemplate.cs b/Apis/email-data/Migrations/20260608125339_AddLocationToScanSummaryTemplate.cs new file mode 100644 index 0000000..9b73100 --- /dev/null +++ b/Apis/email-data/Migrations/20260608125339_AddLocationToScanSummaryTemplate.cs @@ -0,0 +1,80 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Email.Data.Migrations +{ + /// + public partial class AddLocationToScanSummaryTemplate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.UpdateData( + schema: MigrationConstants.SchemaName, + table: "Templates", + keyColumns: ["Key", "Language"], + keyValues: ["email.search-results.scan-summary", "en"], + columns: ["Value"], + values: [@" + + + +
+
Keywords used: {{keywordsHtml}}
+
Location: {{location}}
+
Providers scanned: {{providers}}
+
"]); + + migrationBuilder.UpdateData( + schema: MigrationConstants.SchemaName, + table: "Templates", + keyColumns: ["Key", "Language"], + keyValues: ["email.search-results.scan-summary", "ro"], + columns: ["Value"], + values: [@" + + + +
+
Cuvinte cheie folosite: {{keywordsHtml}}
+
Locație căutată: {{location}}
+
Furnizori scanați: {{providers}}
+
"]); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.UpdateData( + schema: MigrationConstants.SchemaName, + table: "Templates", + keyColumns: ["Key", "Language"], + keyValues: ["email.search-results.scan-summary", "en"], + columns: ["Value"], + values: [@" + + + +
+
Keywords used: {{keywordsHtml}}
+
Providers scanned: {{providers}}
+
"]); + + migrationBuilder.UpdateData( + schema: MigrationConstants.SchemaName, + table: "Templates", + keyColumns: ["Key", "Language"], + keyValues: ["email.search-results.scan-summary", "ro"], + columns: ["Value"], + values: [@" + + + +
+
Cuvinte cheie folosite: {{keywordsHtml}}
+
Furnizori scanați: {{providers}}
+
"]); + } + } +} diff --git a/Jobs/cv-search-job/Services/CvSearchEmailSender.cs b/Jobs/cv-search-job/Services/CvSearchEmailSender.cs index 08fac2b..c665354 100644 --- a/Jobs/cv-search-job/Services/CvSearchEmailSender.cs +++ b/Jobs/cv-search-job/Services/CvSearchEmailSender.cs @@ -46,6 +46,7 @@ public sealed class CvSearchEmailSender IReadOnlyList keywords, IReadOnlyList 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. /// - private string BuildBody(IReadOnlyList results, IReadOnlyList keywords, IReadOnlyList providerNames, string language) + private string BuildBody(IReadOnlyList results, IReadOnlyList keywords, IReadOnlyList 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. /// - private string BuildScanSummary(IReadOnlyList keywords, IReadOnlyList providerNames, string language) + private string BuildScanSummary(IReadOnlyList keywords, IReadOnlyList 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)); } /// diff --git a/Jobs/cv-search-job/Tasks/CvSearchJobTask.cs b/Jobs/cv-search-job/Tasks/CvSearchJobTask.cs index 4b45d44..f867d1c 100644 --- a/Jobs/cv-search-job/Tasks/CvSearchJobTask.cs +++ b/Jobs/cv-search-job/Tasks/CvSearchJobTask.cs @@ -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);