feat(cv-search-job): replace MyAiDbContext+ITemplateService with IEmailTemplateService

- Add ProjectReference to email-api-data; remove myai-data reference
- Program.cs: register EmailApiDbContext (no migrate), IEmailTemplateRepository
  (scoped), IEmailTemplateService (singleton); remove MyAiDbContext +
  ITemplateService registrations and their migration call
- CvSearchEmailSender: inject IEmailTemplateService; replace
  _config["Contact:ToEmail"] with GetOperatorCopy("email.search-results.subject")
  for operator copy logic; remove IConfiguration injection
- docker-compose: remove Contact__ToEmail from cv-search-job service block;
  add Database__* env vars to email-api service (needed for EmailApiDbContext)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 08:45:18 +03:00
parent e7ca6043b7
commit e17f17b566
4 changed files with 29 additions and 29 deletions
+10 -11
View File
@@ -3,6 +3,10 @@ using CvMatcher.Models.Settings;
using CvSearch.Data; using CvSearch.Data;
using CvSearchJob.Clients; using CvSearchJob.Clients;
using CvSearchJob.Services; using CvSearchJob.Services;
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 CvSearchJob.Tasks; using CvSearchJob.Tasks;
using JobScheduler.Scheduling; using JobScheduler.Scheduling;
@@ -10,8 +14,6 @@ using JobScheduler.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using MyAi.Data;
using MyAi.Data.Services;
using Refit; using Refit;
using Serilog; using Serilog;
using Common.Settings; using Common.Settings;
@@ -54,16 +56,18 @@ try
client.DefaultRequestHeaders.Add("X-Internal-Api-Key", key); client.DefaultRequestHeaders.Add("X-Internal-Api-Key", key);
}); });
builder.Services.AddDbContext<MyAiDbContext>(options => builder.Services.AddDbContext<EmailApiDbContext>(options =>
{ {
var connectionString = builder.Services.GetConfiguredDbConnectionString(builder.Configuration); var connectionString = builder.Services.GetConfiguredDbConnectionString(builder.Configuration);
options.UseSqlServer(connectionString, sql => options.UseSqlServer(connectionString, sql =>
{ {
sql.MigrationsAssembly("myai-data"); sql.MigrationsHistoryTable(EmailApiDbContext.MigrationTableName, EmailApiDbContext.SchemaName);
sql.MigrationsHistoryTable(MyAiDbContext.MigrationTableName, MyAiDbContext.SchemaName); sql.MigrationsAssembly("email-api-data");
}); });
}); });
builder.Services.AddSingleton<ITemplateService, DbTemplateService>();
builder.Services.AddScoped<IEmailTemplateRepository, EfEmailTemplateRepository>();
builder.Services.AddSingleton<IEmailTemplateService, EmailTemplateService>();
builder.Services.AddRefitClient<IEmailApiClient>() builder.Services.AddRefitClient<IEmailApiClient>()
.ConfigureHttpClient((sp, client) => .ConfigureHttpClient((sp, client) =>
@@ -98,11 +102,6 @@ try
var db = scope.ServiceProvider.GetRequiredService<CvSearchDbContext>(); var db = scope.ServiceProvider.GetRequiredService<CvSearchDbContext>();
db.Database.Migrate(); db.Database.Migrate();
} }
using (var scope = host.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<MyAiDbContext>();
db.Database.Migrate();
}
Log.Information("{Service} startup complete. Background scheduler is running.", ServiceName); Log.Information("{Service} startup complete. Background scheduler is running.", ServiceName);
await host.RunAsync(); await host.RunAsync();
@@ -1,29 +1,25 @@
using CvMatcher.Models.Responses; using CvMatcher.Models.Responses;
using CvSearch.Data.Entities; using CvSearch.Data.Entities;
using EmailApi.Data.Services;
using EmailApi.Models.Clients; using EmailApi.Models.Clients;
using EmailApi.Models.Requests; using EmailApi.Models.Requests;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MyAi.Data.Services;
namespace CvSearchJob.Services; namespace CvSearchJob.Services;
public sealed class CvSearchEmailSender public sealed class CvSearchEmailSender
{ {
private readonly IEmailApiClient _emailApi; private readonly IEmailApiClient _emailApi;
private readonly ITemplateService _templates; private readonly IEmailTemplateService _emailTemplates;
private readonly IConfiguration _config;
private readonly ILogger<CvSearchEmailSender> _logger; private readonly ILogger<CvSearchEmailSender> _logger;
public CvSearchEmailSender( public CvSearchEmailSender(
IEmailApiClient emailApi, IEmailApiClient emailApi,
ITemplateService templates, IEmailTemplateService emailTemplates,
IConfiguration config,
ILogger<CvSearchEmailSender> logger) ILogger<CvSearchEmailSender> logger)
{ {
_emailApi = emailApi; _emailApi = emailApi;
_templates = templates; _emailTemplates = emailTemplates;
_config = config;
_logger = logger; _logger = logger;
} }
@@ -34,18 +30,18 @@ public sealed class CvSearchEmailSender
string language, string language,
CancellationToken ct) CancellationToken ct)
{ {
var contactToEmail = _config["Contact:ToEmail"]; var operatorCopy = _emailTemplates.GetOperatorCopy("email.search-results.subject", language);
var recipients = new List<string>(); var recipients = new List<string>();
if (!string.IsNullOrWhiteSpace(toEmail)) recipients.Add(toEmail); if (!string.IsNullOrWhiteSpace(toEmail)) recipients.Add(toEmail);
if (!string.IsNullOrWhiteSpace(contactToEmail) && if (!string.IsNullOrWhiteSpace(operatorCopy) &&
!recipients.Any(r => string.Equals(r, contactToEmail, StringComparison.OrdinalIgnoreCase))) !recipients.Any(r => string.Equals(r, operatorCopy, StringComparison.OrdinalIgnoreCase)))
recipients.Add(contactToEmail); recipients.Add(operatorCopy);
if (recipients.Count == 0) return; if (recipients.Count == 0) return;
var htmlBody = BuildBody(results, language); var htmlBody = BuildBody(results, language);
var subject = _templates.Render("email.search-results.subject", language, var subject = _emailTemplates.Render("email.search-results.subject", language,
("count", results.Count.ToString())); ("count", results.Count.ToString()));
try try
@@ -71,7 +67,7 @@ public sealed class CvSearchEmailSender
private string BuildBody(IReadOnlyList<JobSearchResultEntity> results, string language) private string BuildBody(IReadOnlyList<JobSearchResultEntity> results, string language)
{ {
if (results.Count == 0) if (results.Count == 0)
return _templates.Get("email.search-results.empty", language); return _emailTemplates.Get("email.search-results.empty", language);
var items = new System.Text.StringBuilder(); var items = new System.Text.StringBuilder();
for (int i = 0; i < results.Count; i++) for (int i = 0; i < results.Count; i++)
@@ -91,7 +87,7 @@ public sealed class CvSearchEmailSender
"""); """);
} }
return _templates.Render("email.search-results.body", language, return _emailTemplates.Render("email.search-results.body", language,
("count", results.Count.ToString()), ("count", results.Count.ToString()),
("items", items.ToString())); ("items", items.ToString()));
} }
+1 -1
View File
@@ -21,12 +21,12 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Apis\cv-matcher-api-models\cv-matcher-api-models.csproj" /> <ProjectReference Include="..\..\Apis\cv-matcher-api-models\cv-matcher-api-models.csproj" />
<ProjectReference Include="..\..\Apis\email-api-data\email-api-data.csproj" />
<ProjectReference Include="..\..\Apis\email-api-models\email-api-models.csproj" /> <ProjectReference Include="..\..\Apis\email-api-models\email-api-models.csproj" />
<ProjectReference Include="..\..\Apis\cv-search-data\cv-search-data.csproj" /> <ProjectReference Include="..\..\Apis\cv-search-data\cv-search-data.csproj" />
<ProjectReference Include="..\..\Apis\common\common.csproj" /> <ProjectReference Include="..\..\Apis\common\common.csproj" />
<ProjectReference Include="..\..\Helpers\startup-helpers\startup-helpers.csproj" /> <ProjectReference Include="..\..\Helpers\startup-helpers\startup-helpers.csproj" />
<ProjectReference Include="..\job-scheduler\job-scheduler.csproj" /> <ProjectReference Include="..\job-scheduler\job-scheduler.csproj" />
<ProjectReference Include="..\..\Apis\myai-data\myai-data.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+7 -2
View File
@@ -108,6 +108,13 @@ services:
- ASPNETCORE_URLS=${ASPNETCORE_URLS:-http://+:8080} - ASPNETCORE_URLS=${ASPNETCORE_URLS:-http://+:8080}
- APP_ENVIRONMENT_NAME=${APP_ENVIRONMENT_NAME:-myai.staging} - APP_ENVIRONMENT_NAME=${APP_ENVIRONMENT_NAME:-myai.staging}
- Database__Host=${Database__Host:-sqlserver}
- Database__Port=${Database__Port:-1433}
- Database__Name=${Database__Name:-MyAiDb}
- Database__User=${Database__User:-sa}
- Database__Password=${Database__Password:-}
- Database__TrustServerCertificate=${Database__TrustServerCertificate:-true}
- InternalApi__ApiKey=${EmailApi__InternalApiKey:-} - InternalApi__ApiKey=${EmailApi__InternalApiKey:-}
- InternalApi__RequireApiKey=true - InternalApi__RequireApiKey=true
@@ -261,8 +268,6 @@ services:
- EmailApi__BaseUrl=${EmailApi__BaseUrl:-http://email-api:8080} - EmailApi__BaseUrl=${EmailApi__BaseUrl:-http://email-api:8080}
- EmailApi__InternalApiKey=${EmailApi__InternalApiKey:-} - EmailApi__InternalApiKey=${EmailApi__InternalApiKey:-}
- Contact__ToEmail=${Contact__ToEmail:-}
- FileStorage__Path=${FileStorage__Path:-Files} - FileStorage__Path=${FileStorage__Path:-Files}
- JobSearch__Enabled=${JobSearch__Enabled:-true} - JobSearch__Enabled=${JobSearch__Enabled:-true}