diff --git a/Apis/myai-data/Migrations/20260528120000_DeleteMigratedTemplates.Designer.cs b/Apis/myai-data/Migrations/20260528120000_DeleteMigratedTemplates.Designer.cs
new file mode 100644
index 0000000..d41e5cf
--- /dev/null
+++ b/Apis/myai-data/Migrations/20260528120000_DeleteMigratedTemplates.Designer.cs
@@ -0,0 +1,62 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using MyAi.Data;
+
+#nullable disable
+
+namespace MyAi.Data.Migrations
+{
+ [DbContext(typeof(MyAiDbContext))]
+ [Migration("20260528120000_DeleteMigratedTemplates")]
+ partial class DeleteMigratedTemplates
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasDefaultSchema("myAi")
+ .HasAnnotation("ProductVersion", "10.0.7")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("MyAi.Data.Entities.TemplateEntity", 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("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("datetime2")
+ .HasDefaultValueSql("SYSUTCDATETIME()");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Key", "Language");
+
+ b.ToTable("Templates", "myAi");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Apis/myai-data/Migrations/20260528120000_DeleteMigratedTemplates.cs b/Apis/myai-data/Migrations/20260528120000_DeleteMigratedTemplates.cs
new file mode 100644
index 0000000..426c592
--- /dev/null
+++ b/Apis/myai-data/Migrations/20260528120000_DeleteMigratedTemplates.cs
@@ -0,0 +1,24 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace MyAi.Data.Migrations
+{
+ ///
+ public partial class DeleteMigratedTemplates : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.Sql("DELETE FROM [myAi].[Templates] WHERE [Key] LIKE 'email.%'");
+ migrationBuilder.Sql("DELETE FROM [myAi].[Templates] WHERE [Key] LIKE 'ai.%'");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ // Rows were migrated to emailApi.EmailTemplates and cvMatcher.AiPrompts.
+ // Re-inserting them here is intentionally omitted.
+ }
+ }
+}
diff --git a/CLAUDE.md b/CLAUDE.md
index 6443417..948be91 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -65,16 +65,17 @@ Apis/
api-models/ DTOs and settings for api only.
email-api/ Internal SMTP email relay (no public port). All email sending goes here.
email-api-models/ Refit client + SendEmailRequest + EmailApiSettings (shared by api and cv-search-job).
- cv-matcher-api/ Internal CV match engine (port 8082). Runs CvMatcher + CvSearch + MyAi DB migrations.
+ cv-matcher-api/ Internal CV match engine (port 8082). Runs CvMatcher + CvSearch DB migrations.
cv-matcher-api-models/ DTOs shared between api and cv-matcher-api (incl. JobSearchSettings).
rag-api/ Internal RAG/vector-search service (port 8081).
rag-api-models/ DTOs shared with rag-api.
common/ Cross-service infrastructure primitives (DatabaseSettings, InternalApiSettings, etc.).
shared-data/ Abstract BaseEntity base class. No DbContext.
- cv-matcher-data/ CvMatcherDbContext + entities + migrations (schema: cvMatcher).
+ cv-matcher-data/ CvMatcherDbContext + entities + migrations (schema: cvMatcher). Owns AiPrompts table.
cv-search-data/ CvSearchDbContext + entities + migrations (schema: cvSearch).
+ email-api-data/ EmailApiDbContext + entities + migrations (schema: emailApi). Owns EmailTemplates table.
rag-data/ RagDbContext + entities + migrations (schema: rag).
- myai-data/ MyAiDbContext + entities + migrations (schema: myAi).
+ myai-data/ MyAiDbContext + entities + migrations (schema: myAi). Keeps only html.* templates.
Helpers/
startup-helpers/ Shared Program.cs bootstrap: Serilog, Swagger, .env loading, Azure Key Vault, middleware.
common-helpers/ Utility helpers.
@@ -110,12 +111,15 @@ Config lives in `docker-compose/.env`. All env vars use `${VAR:-default}` fallba
| Schema | Owner DbContext | Migrations project | Startup project |
|-------------|----------------------|-----------------------|-----------------------|
| `cvMatcher` | `CvMatcherDbContext` | `cv-matcher-data` | `cv-matcher-api` |
+| `emailApi` | `EmailApiDbContext` | `email-api-data` | `email-api` |
| `rag` | `RagDbContext` | `rag-data` | `rag-api` |
| `cvSearch` | `CvSearchDbContext` | `cv-search-data` | `cv-matcher-api` |
| `myAi` | `MyAiDbContext` | `myai-data` | `api` |
Both `cv-matcher-api` and `cv-search-job` register `CvSearchDbContext` and call `db.Database.Migrate()` on startup (idempotent — safe for both to run).
+`api` and `cv-search-job` also register `EmailApiDbContext` (read-only — `email-api` is the sole migration owner). They use it to load email templates via `IEmailTemplateService` (10-min cache, singleton).
+
## EF Core migrations
```powershell
@@ -125,6 +129,12 @@ dotnet ef migrations add `
--project Apis/cv-matcher-data `
--startup-project Apis/cv-matcher-api
+# email-api-data (schema: emailApi)
+dotnet ef migrations add `
+ --context EmailApiDbContext `
+ --project Apis/email-api-data `
+ --startup-project Apis/email-api
+
# rag-data (schema: rag)
dotnet ef migrations add `
--context RagDbContext `