diff --git a/Jobs/cv-search-job/Program.cs b/Jobs/cv-search-job/Program.cs index 1bdf24a..59abf8d 100644 --- a/Jobs/cv-search-job/Program.cs +++ b/Jobs/cv-search-job/Program.cs @@ -3,6 +3,7 @@ using CvMatcher.Models.Settings; using CvSearch.Data; using CvSearchJob.Clients; using CvSearchJob.Services; +using EmailApi.Models.Clients; using CvSearchJob.Tasks; using JobScheduler.Scheduling; using JobScheduler.Tasks; @@ -64,6 +65,18 @@ try }); builder.Services.AddSingleton(); + builder.Services.AddRefitClient() + .ConfigureHttpClient((sp, client) => + { + var config = sp.GetRequiredService(); + var baseUrl = config["EmailApi:BaseUrl"] ?? string.Empty; + if (!string.IsNullOrWhiteSpace(baseUrl)) + client.BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/"); + var key = config["EmailApi:InternalApiKey"]; + if (!string.IsNullOrWhiteSpace(key)) + client.DefaultRequestHeaders.Add("X-Internal-Api-Key", key); + }); + builder.Services.AddHttpClient(); builder.Services.AddSingleton(); diff --git a/Jobs/cv-search-job/Services/CvSearchEmailSender.cs b/Jobs/cv-search-job/Services/CvSearchEmailSender.cs index 58ef994..8eeedc6 100644 --- a/Jobs/cv-search-job/Services/CvSearchEmailSender.cs +++ b/Jobs/cv-search-job/Services/CvSearchEmailSender.cs @@ -1,43 +1,41 @@ using CvMatcher.Models.Responses; using CvSearch.Data.Entities; -using MailKit.Net.Smtp; -using MailKit.Security; +using EmailApi.Models.Clients; +using EmailApi.Models.Requests; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using MimeKit; using MyAi.Data.Services; namespace CvSearchJob.Services; public sealed class CvSearchEmailSender { - private readonly IConfiguration _config; + private readonly IEmailApiClient _emailApi; private readonly ITemplateService _templates; + private readonly IConfiguration _config; private readonly ILogger _logger; - public CvSearchEmailSender(IConfiguration config, ITemplateService templates, ILogger logger) + public CvSearchEmailSender( + IEmailApiClient emailApi, + ITemplateService templates, + IConfiguration config, + ILogger logger) { - _config = config; + _emailApi = emailApi; _templates = templates; + _config = config; _logger = logger; } public async Task SendResultsAsync( string toEmail, - string? attachmentPath, + string? attachmentFileName, IReadOnlyList results, string language, CancellationToken ct) { - var smtpHost = _config["Smtp:Host"]; - var smtpPort = int.TryParse(_config["Smtp:Port"], out var port) ? port : 587; - var smtpUser = _config["Smtp:Username"]; - var smtpPass = _config["Smtp:Password"]; - var useStartTls = bool.TryParse(_config["Smtp:UseStartTls"], out var tls) && tls; var contactToEmail = _config["Contact:ToEmail"]; - if (string.IsNullOrWhiteSpace(smtpHost)) return; - var recipients = new List(); if (!string.IsNullOrWhiteSpace(toEmail)) recipients.Add(toEmail); if (!string.IsNullOrWhiteSpace(contactToEmail) && @@ -46,39 +44,27 @@ public sealed class CvSearchEmailSender if (recipients.Count == 0) return; - var body = BuildBody(results, language); + var htmlBody = BuildBody(results, language); var subject = _templates.Render("email.search-results.subject", language, ("count", results.Count.ToString())); - var environmentName = Environment.GetEnvironmentVariable("APP_ENVIRONMENT_NAME") ?? "Development"; - foreach (var recipient in recipients) + try { - var msg = new MimeMessage(); - msg.From.Add(MailboxAddress.Parse(smtpUser!)); - msg.To.Add(MailboxAddress.Parse(recipient)); - msg.Subject = $"[{environmentName}] {subject}"; - - var builder = new BodyBuilder { TextBody = body }; - if (!string.IsNullOrWhiteSpace(attachmentPath) && File.Exists(attachmentPath)) - builder.Attachments.Add(attachmentPath); - - msg.Body = builder.ToMessageBody(); - - try + await _emailApi.SendAsync(new SendEmailRequest { - using var client = new SmtpClient(); - var tls2 = useStartTls ? SecureSocketOptions.StartTls : SecureSocketOptions.Auto; - await client.ConnectAsync(smtpHost, smtpPort, tls2, ct); - if (!string.IsNullOrWhiteSpace(smtpUser)) - await client.AuthenticateAsync(smtpUser, smtpPass ?? string.Empty, ct); - await client.SendAsync(msg, ct); - await client.DisconnectAsync(true, ct); - _logger.LogInformation("Job search results email sent to {Recipient}", recipient); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to send job search results email to {Recipient}", recipient); - } + To = recipients, + Subject = subject, + HtmlBody = htmlBody, + AttachmentPath = attachmentFileName + }, ct); + + _logger.LogInformation("Job search results email sent to {Recipients}", + string.Join(", ", recipients)); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to send job search results email to {Recipients}", + string.Join(", ", recipients)); } } @@ -92,11 +78,17 @@ public sealed class CvSearchEmailSender { var r = results[i]; var matchResp = TryParseResult(r.ResultJson); - items.AppendLine($"{i + 1}. {r.JobTitle} ({r.Score}% match) [{r.ProviderName}]"); - items.AppendLine($" {r.JobUrl}"); - if (matchResp is not null && !string.IsNullOrWhiteSpace(matchResp.Summary)) - items.AppendLine($" {matchResp.Summary}"); - items.AppendLine(); + var summary = matchResp?.Summary; + + items.Append($""" +
+ {i + 1}. {r.JobTitle} + {r.Score}% match + [{r.ProviderName}]
+ {r.JobUrl} + {(string.IsNullOrWhiteSpace(summary) ? "" : $"

{summary}

")} +
+ """); } return _templates.Render("email.search-results.body", language, @@ -106,7 +98,11 @@ public sealed class CvSearchEmailSender private static JobMatchResponse? TryParseResult(string json) { - try { return System.Text.Json.JsonSerializer.Deserialize(json, new System.Text.Json.JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults.Web)); } + try + { + return System.Text.Json.JsonSerializer.Deserialize(json, + new System.Text.Json.JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults.Web)); + } catch { return null; } } } diff --git a/Jobs/cv-search-job/Tasks/CvSearchJobTask.cs b/Jobs/cv-search-job/Tasks/CvSearchJobTask.cs index 7993736..593baf7 100644 --- a/Jobs/cv-search-job/Tasks/CvSearchJobTask.cs +++ b/Jobs/cv-search-job/Tasks/CvSearchJobTask.cs @@ -22,7 +22,6 @@ public sealed class CvSearchJobTask : IJobTask private readonly ICvMatcherInternalApi _matcherApi; private readonly CvSearchEmailSender _emailSender; private readonly ILogger _logger; - private readonly string _fileStoragePath; public string TaskType => "CvSearch"; @@ -32,7 +31,6 @@ public sealed class CvSearchJobTask : IJobTask HtmlJobSearcher searcher, ICvMatcherInternalApi matcherApi, CvSearchEmailSender emailSender, - IConfiguration config, ILogger logger) { _scopeFactory = scopeFactory; @@ -41,9 +39,6 @@ public sealed class CvSearchJobTask : IJobTask _matcherApi = matcherApi; _emailSender = emailSender; _logger = logger; - _fileStoragePath = config["FileStorage:Path"] ?? "Files"; - if (!Path.IsPathRooted(_fileStoragePath)) - _fileStoragePath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), _fileStoragePath)); } public async Task ExecuteAsync(IConfiguration parametersSection, CancellationToken cancellationToken) @@ -85,8 +80,8 @@ public sealed class CvSearchJobTask : IJobTask pending.Status = JobSearchStatus.Done; await db.SaveChangesAsync(cancellationToken); - var attachmentPath = BuildCvPath(pending.CvDocumentId); - await _emailSender.SendResultsAsync(pending.Email, attachmentPath, results, pending.Language, cancellationToken); + var attachmentFileName = BuildCvFileName(pending.CvDocumentId); + await _emailSender.SendResultsAsync(pending.Email, attachmentFileName, results, pending.Language, cancellationToken); _logger.LogInformation("Session {SessionId} done. {Count} results sent.", pending.Id, results.Count); } catch (Exception ex) @@ -194,10 +189,10 @@ public sealed class CvSearchJobTask : IJobTask return Uri.TryCreate(url, UriKind.Absolute, out var uri) ? uri.Host : "unknown"; } - private string BuildCvPath(string cvDocumentId) + private static string BuildCvFileName(string cvDocumentId) { var safeId = string.Concat(cvDocumentId.Where(char.IsLetterOrDigit)); if (string.IsNullOrWhiteSpace(safeId)) safeId = "cv"; - return Path.Combine(_fileStoragePath, $"{safeId}.pdf"); + return $"{safeId}.pdf"; } } diff --git a/Jobs/cv-search-job/cv-search-job.csproj b/Jobs/cv-search-job/cv-search-job.csproj index 7a695c2..94657fc 100644 --- a/Jobs/cv-search-job/cv-search-job.csproj +++ b/Jobs/cv-search-job/cv-search-job.csproj @@ -9,7 +9,7 @@ - + @@ -21,6 +21,7 @@ +