feat(api): wire IEmailTemplateService; replace Contact:ToEmail with OperatorCopy
- Add ProjectReference to email-api-data
- Register EmailApiDbContext (no migrate — email-api owns migrations)
- Register IEmailTemplateRepository (scoped) and IEmailTemplateService (singleton)
- EmailApiEmailSender: replace ITemplateService with IEmailTemplateService for
all email.* template rendering (match body/subject/footer)
- SendMatchAsync: replace _contact.ToEmail operator copy with
GetOperatorCopy("email.match.subject", "en") from DB template
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Api.Services;
|
using Api.Services;
|
||||||
using Api.Services.Contracts;
|
using Api.Services.Contracts;
|
||||||
|
using EmailApi.Data;
|
||||||
|
using EmailApi.Data.Repositories;
|
||||||
|
using EmailApi.Data.Repositories.Contracts;
|
||||||
|
using EmailApi.Data.Services;
|
||||||
using EmailApi.Models.Clients;
|
using EmailApi.Models.Clients;
|
||||||
using EmailApi.Models.Settings;
|
using EmailApi.Models.Settings;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@@ -47,6 +51,19 @@ try
|
|||||||
});
|
});
|
||||||
builder.Services.AddSingleton<ITemplateService, DbTemplateService>();
|
builder.Services.AddSingleton<ITemplateService, DbTemplateService>();
|
||||||
|
|
||||||
|
builder.Services.AddDbContext<EmailApiDbContext>(options =>
|
||||||
|
{
|
||||||
|
var connectionString = builder.Services.GetConfiguredDbConnectionString(builder.Configuration);
|
||||||
|
options.UseSqlServer(connectionString, sql =>
|
||||||
|
{
|
||||||
|
sql.MigrationsHistoryTable(EmailApiDbContext.MigrationTableName, EmailApiDbContext.SchemaName);
|
||||||
|
sql.MigrationsAssembly("email-api-data");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddScoped<IEmailTemplateRepository, EfEmailTemplateRepository>();
|
||||||
|
builder.Services.AddSingleton<IEmailTemplateService, EmailTemplateService>();
|
||||||
|
|
||||||
builder.Services.AddHttpClient<ICaptchaVerifier, RecaptchaVerifier>();
|
builder.Services.AddHttpClient<ICaptchaVerifier, RecaptchaVerifier>();
|
||||||
builder.Services.AddSingleton<IEmailSender, EmailApiEmailSender>();
|
builder.Services.AddSingleton<IEmailSender, EmailApiEmailSender>();
|
||||||
builder.Services.AddSingleton<Microsoft.AspNetCore.StaticFiles.IContentTypeProvider, Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider>();
|
builder.Services.AddSingleton<Microsoft.AspNetCore.StaticFiles.IContentTypeProvider, Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider>();
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
using Api.Services.Contracts;
|
using Api.Services.Contracts;
|
||||||
using CvMatcher.Models.Responses;
|
using CvMatcher.Models.Responses;
|
||||||
|
using EmailApi.Data.Services;
|
||||||
using EmailApi.Models.Clients;
|
using EmailApi.Models.Clients;
|
||||||
using EmailApi.Models.Requests;
|
using EmailApi.Models.Requests;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Models.Requests;
|
using Models.Requests;
|
||||||
using Models.Settings;
|
using Models.Settings;
|
||||||
using MyAi.Data.Services;
|
|
||||||
|
|
||||||
namespace Api.Services;
|
namespace Api.Services;
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ public sealed class EmailApiEmailSender : IEmailSender
|
|||||||
private readonly ContactSettings _contact;
|
private readonly ContactSettings _contact;
|
||||||
private readonly SubscribeSettings _subscribe;
|
private readonly SubscribeSettings _subscribe;
|
||||||
private readonly FileStorageSettings _fileStorage;
|
private readonly FileStorageSettings _fileStorage;
|
||||||
private readonly ITemplateService _templates;
|
private readonly IEmailTemplateService _emailTemplates;
|
||||||
private readonly ILogger<EmailApiEmailSender> _log;
|
private readonly ILogger<EmailApiEmailSender> _log;
|
||||||
|
|
||||||
public EmailApiEmailSender(
|
public EmailApiEmailSender(
|
||||||
@@ -23,14 +23,14 @@ public sealed class EmailApiEmailSender : IEmailSender
|
|||||||
IOptions<ContactSettings> contact,
|
IOptions<ContactSettings> contact,
|
||||||
IOptions<SubscribeSettings> subscribe,
|
IOptions<SubscribeSettings> subscribe,
|
||||||
IOptions<FileStorageSettings> fileStorage,
|
IOptions<FileStorageSettings> fileStorage,
|
||||||
ITemplateService templates,
|
IEmailTemplateService emailTemplates,
|
||||||
ILogger<EmailApiEmailSender> log)
|
ILogger<EmailApiEmailSender> log)
|
||||||
{
|
{
|
||||||
_emailApi = emailApi;
|
_emailApi = emailApi;
|
||||||
_contact = contact.Value;
|
_contact = contact.Value;
|
||||||
_subscribe = subscribe.Value;
|
_subscribe = subscribe.Value;
|
||||||
_fileStorage = fileStorage.Value;
|
_fileStorage = fileStorage.Value;
|
||||||
_templates = templates;
|
_emailTemplates = emailTemplates;
|
||||||
_log = log;
|
_log = log;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,13 +148,15 @@ public sealed class EmailApiEmailSender : IEmailSender
|
|||||||
|
|
||||||
public async Task SendMatchAsync(string? explicitTo, string subject, string body, string? attachmentPath, CancellationToken ct)
|
public async Task SendMatchAsync(string? explicitTo, string subject, string body, string? attachmentPath, CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
var operatorCopy = _emailTemplates.GetOperatorCopy("email.match.subject", "en");
|
||||||
|
|
||||||
var recipients = new List<string>();
|
var recipients = new List<string>();
|
||||||
if (!string.IsNullOrWhiteSpace(explicitTo))
|
if (!string.IsNullOrWhiteSpace(explicitTo))
|
||||||
recipients.Add(explicitTo);
|
recipients.Add(explicitTo);
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(_contact.ToEmail) &&
|
if (!string.IsNullOrWhiteSpace(operatorCopy) &&
|
||||||
!recipients.Any(x => string.Equals(x, _contact.ToEmail, StringComparison.OrdinalIgnoreCase)))
|
!recipients.Any(x => string.Equals(x, operatorCopy, StringComparison.OrdinalIgnoreCase)))
|
||||||
recipients.Add(_contact.ToEmail);
|
recipients.Add(operatorCopy);
|
||||||
|
|
||||||
if (recipients.Count == 0)
|
if (recipients.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -199,7 +201,7 @@ public sealed class EmailApiEmailSender : IEmailSender
|
|||||||
string.Join("", result.Recommendations.Select(r => $"<li>{r}</li>")) + "</ul>"
|
string.Join("", result.Recommendations.Select(r => $"<li>{r}</li>")) + "</ul>"
|
||||||
: "<p style=\"color:#6c757d\">—</p>";
|
: "<p style=\"color:#6c757d\">—</p>";
|
||||||
|
|
||||||
var body = _templates.Render("email.match.body", language,
|
var body = _emailTemplates.Render("email.match.body", language,
|
||||||
("cvDocumentId", cvDocumentId),
|
("cvDocumentId", cvDocumentId),
|
||||||
("jobLabel", jobLabel ?? "N/A"),
|
("jobLabel", jobLabel ?? "N/A"),
|
||||||
("jobUrl", result.JobUrl ?? "N/A"),
|
("jobUrl", result.JobUrl ?? "N/A"),
|
||||||
@@ -211,7 +213,7 @@ public sealed class EmailApiEmailSender : IEmailSender
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(jobSearchLink))
|
if (!string.IsNullOrWhiteSpace(jobSearchLink))
|
||||||
{
|
{
|
||||||
body += _templates.Render("email.match.job-search-footer", language,
|
body += _emailTemplates.Render("email.match.job-search-footer", language,
|
||||||
("jobSearchLink", jobSearchLink),
|
("jobSearchLink", jobSearchLink),
|
||||||
("expiryDays", expiryDays.ToString()));
|
("expiryDays", expiryDays.ToString()));
|
||||||
}
|
}
|
||||||
@@ -220,7 +222,7 @@ public sealed class EmailApiEmailSender : IEmailSender
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string BuildMatchEmailSubject(int score, string? jobLabel, string language) =>
|
public string BuildMatchEmailSubject(int score, string? jobLabel, string language) =>
|
||||||
_templates.Render("email.match.subject", language,
|
_emailTemplates.Render("email.match.subject", language,
|
||||||
("score", score.ToString()),
|
("score", score.ToString()),
|
||||||
("jobLabel", jobLabel ?? "Job"));
|
("jobLabel", jobLabel ?? "Job"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\api-models\api-models.csproj" />
|
<ProjectReference Include="..\api-models\api-models.csproj" />
|
||||||
|
<ProjectReference Include="..\email-api-data\email-api-data.csproj" />
|
||||||
<ProjectReference Include="..\email-api-models\email-api-models.csproj" />
|
<ProjectReference Include="..\email-api-models\email-api-models.csproj" />
|
||||||
<ProjectReference Include="..\cv-matcher-api-models\cv-matcher-api-models.csproj" />
|
<ProjectReference Include="..\cv-matcher-api-models\cv-matcher-api-models.csproj" />
|
||||||
<ProjectReference Include="..\common\common.csproj" />
|
<ProjectReference Include="..\common\common.csproj" />
|
||||||
|
|||||||
Reference in New Issue
Block a user