8 Commits

Author SHA1 Message Date
claude e14a6a0f69 refactor(migrations): extract schema constant for clarity
Build and Push Docker Images Staging / build (push) Successful in 8m22s
2026-05-29 10:16:18 +03:00
claude 181a0b23b5 fix(email-data): update migration files to use MigrationConstants.SchemaName
Fix schema name references in migration Designer.cs and ModelSnapshot files.
Previously these files contained hardcoded 'emailApi' schema name instead of
using MigrationConstants.SchemaName constant. This was causing EF Core to
detect pending model changes and fail migrations.

Changes:
- 20260528100000_CreateEmailTemplates.Designer.cs: Use MigrationConstants.SchemaName
- 20260528130652_SeedEmailTemplates.Designer.cs: Use MigrationConstants.SchemaName
- EmailApiDbContextModelSnapshot.cs: Use MigrationConstants.SchemaName and updated namespace

Also updated entity namespace references from EmailApi.Data to Email.Data.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-29 10:11:31 +03:00
claude 2b43ec5984 fix(docker): update Dockerfile references from email-api-data to email-data
Update all Dockerfile COPY commands to reference the renamed email-data project
instead of email-api-data. This resolves Docker build failures introduced by the
email-api-data → email-data rename.

- Apis/api/Dockerfile: Update lines 8 and 20
- Apis/email-api/Dockerfile: Update lines 6 and 17
- Jobs/cv-search-job/Dockerfile: Update lines 10 and 23

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-29 10:05:35 +03:00
claude ea9bc87981 refactor(data): rename email-api-data to email-data for consistent naming
- Rename project folder Apis/email-api-data → Apis/email-data
- Rename csproj file: email-api-data.csproj → email-data.csproj
- Update csproj properties: AssemblyName and RootNamespace (email-data, Email.Data)
- Update C# namespaces: EmailApi.Data → Email.Data across all email-data files
- Update project references in api.csproj and email-api.csproj
- Update migration assembly references in api/Program.cs and email-api/Program.cs
- Update cv-search-job references to use email-data project and Email.Data namespace
- Update solution file to reference new email-data project path
- Remove hardcoded schema name from SmtpEmailDispatcher, use template service instead

This maintains consistency with other data project naming convention (no service-type suffix).
All tests passing, build succeeds.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-29 09:51:03 +03:00
claude 33d92551da Remove duplicate Data folders from models projects
- Delete cv-search-models/Data (duplicate of cv-search-data/Data)
- Delete myai-models/Data (duplicate of myai-data/Data)
- DbContext and Entities belong only in -data projects, not -models

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-29 09:45:42 +03:00
claude 43017036fd Move CV matcher repositories from cv-matcher-api to cv-matcher-data
- Move IAiPromptsRepository, EfAiPromptsRepository to cv-matcher-data/Repositories
- Move IMatcherRepository, EfMatcherRepository to cv-matcher-data/Repositories
- Add cv-matcher-api-models ProjectReference to cv-matcher-data.csproj
- Delete cv-matcher-api/Data folder (all data access now in cv-matcher-data)

Pattern: cv-matcher-api (logic) → cv-matcher-data (repositories, EF entities, migrations)
Aligns with rag-api → rag-data consolidation

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-29 09:44:57 +03:00
claude 707f547014 Rename email schema from 'emailApi' to 'email' — consistent naming convention
- Update MigrationConstants.SchemaName in email-api-data from 'emailApi' to 'email'
- All migrations automatically use the new schema name via MigrationConstants reference
- Aligns with naming convention: 'email', 'rag', 'cvMatcher', 'cvSearch', 'myAi'

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-29 09:40:49 +03:00
claude 487924e345 Move RAG repository from rag-api to rag-data — consolidate data layer ownership
- Move IRagRepository, EfRagRepository, and VectorSerializer from rag-api/Data to rag-data/Repositories
- Add rag-api-models ProjectReference to rag-data.csproj for model type availability
- Delete rag-api/Data folder (no longer needed; all data access is now in rag-data)
- This aligns RAG with email-api and other services: all data code in the data project

Pattern: rag-api (API logic) → rag-data (repository, EF entities, migrations)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-29 09:38:25 +03:00
58 changed files with 189 additions and 264 deletions
+2 -2
View File
@@ -5,7 +5,7 @@ WORKDIR /src
COPY Directory.Packages.props ./
COPY Apis/api/api.csproj Apis/api/
COPY Apis/api-models/api-models.csproj Apis/api-models/
COPY Apis/email-api-data/email-api-data.csproj Apis/email-api-data/
COPY Apis/email-data/email-data.csproj Apis/email-data/
COPY Apis/email-api-models/email-api-models.csproj Apis/email-api-models/
COPY Apis/cv-matcher-api-models/cv-matcher-api-models.csproj Apis/cv-matcher-api-models/
COPY Apis/common/common.csproj Apis/common/
@@ -17,7 +17,7 @@ RUN dotnet restore Apis/api/api.csproj
COPY Apis/api/ Apis/api/
COPY Apis/api-models/ Apis/api-models/
COPY Apis/email-api-data/ Apis/email-api-data/
COPY Apis/email-data/ Apis/email-data/
COPY Apis/email-api-models/ Apis/email-api-models/
COPY Apis/cv-matcher-api-models/ Apis/cv-matcher-api-models/
COPY Apis/common/ Apis/common/
+5 -5
View File
@@ -1,10 +1,10 @@
using System.Reflection;
using Api.Services;
using Api.Services.Contracts;
using EmailApi.Data;
using EmailApi.Data.Repositories;
using EmailApi.Data.Repositories.Contracts;
using EmailApi.Data.Services;
using Email.Data;
using Email.Data.Repositories;
using Email.Data.Repositories.Contracts;
using Email.Data.Services;
using EmailApi.Models.Clients;
using EmailApi.Models.Settings;
using Microsoft.EntityFrameworkCore;
@@ -57,7 +57,7 @@ try
options.UseSqlServer(connectionString, sql =>
{
sql.MigrationsHistoryTable(EmailApiDbContext.MigrationTableName, EmailApiDbContext.SchemaName);
sql.MigrationsAssembly("email-api-data");
sql.MigrationsAssembly("email-data");
});
});
+1 -1
View File
@@ -1,6 +1,6 @@
using Api.Services.Contracts;
using CvMatcher.Models.Responses;
using EmailApi.Data.Services;
using Email.Data.Services;
using EmailApi.Models.Clients;
using EmailApi.Models.Requests;
using Microsoft.Extensions.Options;
+1 -1
View File
@@ -36,7 +36,7 @@
<ItemGroup>
<ProjectReference Include="..\api-models\api-models.csproj" />
<ProjectReference Include="..\email-api-data\email-api-data.csproj" />
<ProjectReference Include="..\email-data\email-data.csproj" />
<ProjectReference Include="..\email-api-models\email-api-models.csproj" />
<ProjectReference Include="..\cv-matcher-api-models\cv-matcher-api-models.csproj" />
<ProjectReference Include="..\common\common.csproj" />
+2 -2
View File
@@ -5,8 +5,8 @@ namespace CvMatcher.Data;
public sealed class CvMatcherDbContext : DbContext
{
public const string SchemaName = "cvMatcher";
public const string MigrationTableName = "_Migrations";
public const string SchemaName = MigrationConstants.SchemaName;
public const string MigrationTableName = MigrationConstants.MigrationTableName;
public CvMatcherDbContext(DbContextOptions<CvMatcherDbContext> options) : base(options)
{
@@ -0,0 +1,11 @@
namespace CvMatcher.Data;
/// <summary>
/// Schema constants used by CvMatcherDbContext and migrations.
/// Centralized to avoid hardcoded strings and ensure consistency.
/// </summary>
public static class MigrationConstants
{
public const string SchemaName = "cvMatcher";
public const string MigrationTableName = "_Migrations";
}
@@ -1,5 +1,6 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using CvMatcher.Data;
#nullable disable
@@ -12,11 +13,11 @@ namespace CvMatcher.Data.Migrations
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: "cvMatcher");
name: MigrationConstants.SchemaName);
migrationBuilder.CreateTable(
name: "ChatCache",
schema: "cvMatcher",
schema: MigrationConstants.SchemaName,
columns: table => new
{
CacheKey = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
@@ -32,7 +33,7 @@ namespace CvMatcher.Data.Migrations
migrationBuilder.CreateTable(
name: "Results",
schema: "cvMatcher",
schema: MigrationConstants.SchemaName,
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
@@ -49,7 +50,7 @@ namespace CvMatcher.Data.Migrations
migrationBuilder.CreateIndex(
name: "IX_Results_CvDocumentId_JobDocumentId",
schema: "cvMatcher",
schema: MigrationConstants.SchemaName,
table: "Results",
columns: new[] { "CvDocumentId", "JobDocumentId" },
unique: true);
@@ -60,11 +61,11 @@ namespace CvMatcher.Data.Migrations
{
migrationBuilder.DropTable(
name: "ChatCache",
schema: "cvMatcher");
schema: MigrationConstants.SchemaName);
migrationBuilder.DropTable(
name: "Results",
schema: "cvMatcher");
schema: MigrationConstants.SchemaName);
}
}
}
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore.Migrations;
using CvMatcher.Data;
#nullable disable
@@ -12,7 +13,7 @@ namespace CvMatcher.Data.Migrations
{
migrationBuilder.AddColumn<string>(
name: "Language",
schema: "cvMatcher",
schema: MigrationConstants.SchemaName,
table: "Results",
type: "nvarchar(max)",
nullable: false,
@@ -24,7 +25,7 @@ namespace CvMatcher.Data.Migrations
{
migrationBuilder.DropColumn(
name: "Language",
schema: "cvMatcher",
schema: MigrationConstants.SchemaName,
table: "Results");
}
}
@@ -1,5 +1,6 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using CvMatcher.Data;
#nullable disable
@@ -13,7 +14,7 @@ namespace CvMatcher.Data.Migrations
{
migrationBuilder.CreateTable(
name: "AiPrompts",
schema: "cvMatcher",
schema: MigrationConstants.SchemaName,
columns: table => new
{
Key = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
@@ -28,7 +29,7 @@ namespace CvMatcher.Data.Migrations
});
migrationBuilder.InsertData(
schema: "cvMatcher",
schema: MigrationConstants.SchemaName,
table: "AiPrompts",
columns: ["Key", "Language", "Value", "Description"],
values: new object[]
@@ -43,7 +44,7 @@ namespace CvMatcher.Data.Migrations
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(name: "AiPrompts", schema: "cvMatcher");
migrationBuilder.DropTable(name: "AiPrompts", schema: MigrationConstants.SchemaName);
}
}
}
@@ -4,6 +4,7 @@ using CvMatcher.Data.Entities;
using CvMatcher.Data.Repositories.Contracts;
using CvMatcher.Models.Responses;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace CvMatcher.Data.Repositories;
@@ -15,5 +15,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\shared-data\shared-data.csproj" />
<ProjectReference Include="..\cv-matcher-api-models\cv-matcher-api-models.csproj" />
</ItemGroup>
</Project>
@@ -5,8 +5,8 @@ namespace CvSearch.Data;
public sealed class CvSearchDbContext : DbContext
{
public const string SchemaName = "cvSearch";
public const string MigrationTableName = "_Migrations";
public const string SchemaName = MigrationConstants.SchemaName;
public const string MigrationTableName = MigrationConstants.MigrationTableName;
public CvSearchDbContext(DbContextOptions<CvSearchDbContext> options) : base(options) { }
@@ -0,0 +1,11 @@
namespace CvSearch.Data;
/// <summary>
/// Schema constants used by CvSearchDbContext and migrations.
/// Centralized to avoid hardcoded strings and ensure consistency.
/// </summary>
public static class MigrationConstants
{
public const string SchemaName = "cvSearch";
public const string MigrationTableName = "_Migrations";
}
@@ -1,5 +1,6 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using CvSearch.Data;
#nullable disable
@@ -12,11 +13,11 @@ namespace CvSearch.Data.Migrations
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: "cvSearch");
name: MigrationConstants.SchemaName);
migrationBuilder.CreateTable(
name: "JobSearchResults",
schema: "cvSearch",
schema: MigrationConstants.SchemaName,
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
@@ -36,7 +37,7 @@ namespace CvSearch.Data.Migrations
migrationBuilder.CreateTable(
name: "JobSearchSessions",
schema: "cvSearch",
schema: MigrationConstants.SchemaName,
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
@@ -55,7 +56,7 @@ namespace CvSearch.Data.Migrations
migrationBuilder.CreateTable(
name: "JobSearchTokens",
schema: "cvSearch",
schema: MigrationConstants.SchemaName,
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
@@ -88,15 +89,15 @@ namespace CvSearch.Data.Migrations
{
migrationBuilder.DropTable(
name: "JobSearchResults",
schema: "cvSearch");
schema: MigrationConstants.SchemaName);
migrationBuilder.DropTable(
name: "JobSearchSessions",
schema: "cvSearch");
schema: MigrationConstants.SchemaName);
migrationBuilder.DropTable(
name: "JobSearchTokens",
schema: "cvSearch");
schema: MigrationConstants.SchemaName);
}
}
}
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore.Migrations;
using CvSearch.Data;
#nullable disable
@@ -12,7 +13,7 @@ namespace CvSearch.Data.Migrations
{
migrationBuilder.AddColumn<string>(
name: "Language",
schema: "cvSearch",
schema: MigrationConstants.SchemaName,
table: "JobSearchTokens",
type: "nvarchar(8)",
maxLength: 8,
@@ -21,7 +22,7 @@ namespace CvSearch.Data.Migrations
migrationBuilder.AddColumn<string>(
name: "Language",
schema: "cvSearch",
schema: MigrationConstants.SchemaName,
table: "JobSearchSessions",
type: "nvarchar(8)",
maxLength: 8,
@@ -34,12 +35,12 @@ namespace CvSearch.Data.Migrations
{
migrationBuilder.DropColumn(
name: "Language",
schema: "cvSearch",
schema: MigrationConstants.SchemaName,
table: "JobSearchTokens");
migrationBuilder.DropColumn(
name: "Language",
schema: "cvSearch",
schema: MigrationConstants.SchemaName,
table: "JobSearchSessions");
}
}
@@ -1,62 +0,0 @@
using CvSearch.Models.Data.Entities;
using Microsoft.EntityFrameworkCore;
namespace CvSearch.Models.Data;
public sealed class CvSearchDbContext : DbContext
{
public const string SchemaName = "cvSearch";
public const string MigrationTableName = "_Migrations";
public CvSearchDbContext(DbContextOptions<CvSearchDbContext> options) : base(options) { }
public DbSet<JobSearchTokenEntity> JobSearchTokens => Set<JobSearchTokenEntity>();
public DbSet<JobSearchSessionEntity> JobSearchSessions => Set<JobSearchSessionEntity>();
public DbSet<JobSearchResultEntity> JobSearchResults => Set<JobSearchResultEntity>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema(SchemaName);
modelBuilder.Entity<JobSearchTokenEntity>(entity =>
{
entity.ToTable("JobSearchTokens");
entity.HasKey(x => x.Id);
entity.Property(x => x.Id).HasMaxLength(64);
entity.Property(x => x.CvDocumentId).HasMaxLength(64).IsRequired();
entity.Property(x => x.Email).HasMaxLength(256).IsRequired();
entity.Property(x => x.Language).HasMaxLength(8).HasDefaultValue("en").IsRequired();
entity.Property(x => x.Used).HasDefaultValue(false);
entity.Property(x => x.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
});
modelBuilder.Entity<JobSearchSessionEntity>(entity =>
{
entity.ToTable("JobSearchSessions");
entity.HasKey(x => x.Id);
entity.Property(x => x.Id).HasMaxLength(64);
entity.Property(x => x.TokenId).HasMaxLength(64).IsRequired();
entity.Property(x => x.CvDocumentId).HasMaxLength(64).IsRequired();
entity.Property(x => x.Email).HasMaxLength(256).IsRequired();
entity.Property(x => x.Status).HasMaxLength(32).IsRequired();
entity.Property(x => x.Keywords).HasMaxLength(1000);
entity.Property(x => x.ProviderConfigJson).IsRequired(false);
entity.Property(x => x.Language).HasMaxLength(8).HasDefaultValue("en").IsRequired();
entity.Property(x => x.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
entity.HasIndex(x => x.Status);
});
modelBuilder.Entity<JobSearchResultEntity>(entity =>
{
entity.ToTable("JobSearchResults");
entity.HasKey(x => x.Id);
entity.Property(x => x.Id).HasMaxLength(64);
entity.Property(x => x.SessionId).HasMaxLength(64).IsRequired();
entity.Property(x => x.ProviderName).HasMaxLength(128);
entity.Property(x => x.JobUrl).HasMaxLength(2048);
entity.Property(x => x.JobTitle).HasMaxLength(512);
entity.Property(x => x.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
entity.HasIndex(x => x.SessionId);
});
}
}
@@ -1,14 +0,0 @@
namespace CvSearch.Models.Data.Entities;
public sealed class JobSearchResultEntity
{
public string Id { get; set; } = string.Empty;
public string SessionId { get; set; } = string.Empty;
public string ProviderName { get; set; } = string.Empty;
public string JobUrl { get; set; } = string.Empty;
public string JobTitle { get; set; } = string.Empty;
public string JobText { get; set; } = string.Empty;
public int Score { get; set; }
public string ResultJson { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
@@ -1,22 +0,0 @@
namespace CvSearch.Models.Data.Entities;
public sealed class JobSearchSessionEntity
{
public string Id { get; set; } = string.Empty;
public string TokenId { get; set; } = string.Empty;
public string CvDocumentId { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string Status { get; set; } = JobSearchStatus.Pending;
public string Keywords { get; set; } = string.Empty;
public string? ProviderConfigJson { get; set; }
public string Language { get; set; } = "en";
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
public static class JobSearchStatus
{
public const string Pending = "Pending";
public const string Processing = "Processing";
public const string Done = "Done";
public const string Failed = "Failed";
}
@@ -1,12 +0,0 @@
namespace CvSearch.Models.Data.Entities;
public sealed class JobSearchTokenEntity
{
public string Id { get; set; } = string.Empty;
public string CvDocumentId { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string Language { get; set; } = "en";
public DateTime ExpiresAt { get; set; }
public bool Used { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
+2 -2
View File
@@ -3,7 +3,7 @@ ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY Apis/email-api/email-api.csproj Apis/email-api/
COPY Apis/email-api-data/email-api-data.csproj Apis/email-api-data/
COPY Apis/email-data/email-data.csproj Apis/email-data/
COPY Apis/email-api-models/email-api-models.csproj Apis/email-api-models/
COPY Apis/api-models/api-models.csproj Apis/api-models/
COPY Apis/common/common.csproj Apis/common/
@@ -14,7 +14,7 @@ COPY Directory.Packages.props ./
RUN dotnet restore Apis/email-api/email-api.csproj
COPY Apis/email-api/ Apis/email-api/
COPY Apis/email-api-data/ Apis/email-api-data/
COPY Apis/email-data/ Apis/email-data/
COPY Apis/email-api-models/ Apis/email-api-models/
COPY Apis/api-models/ Apis/api-models/
COPY Apis/common/ Apis/common/
+5 -5
View File
@@ -1,8 +1,8 @@
using System.Reflection;
using EmailApi.Data;
using EmailApi.Data.Repositories;
using EmailApi.Data.Repositories.Contracts;
using EmailApi.Data.Services;
using Email.Data;
using Email.Data.Repositories;
using Email.Data.Repositories.Contracts;
using Email.Data.Services;
using EmailApi.Services;
using Microsoft.EntityFrameworkCore;
using Models.Settings;
@@ -35,7 +35,7 @@ try
options.UseSqlServer(connectionString, sql =>
{
sql.MigrationsHistoryTable(EmailApiDbContext.MigrationTableName, EmailApiDbContext.SchemaName);
sql.MigrationsAssembly("email-api-data");
sql.MigrationsAssembly("email-data");
});
});
@@ -1,4 +1,4 @@
using EmailApi.Data.Services;
using Email.Data.Services;
using EmailApi.Models.Requests;
using MailKit.Net.Smtp;
using MailKit.Security;
+1 -1
View File
@@ -23,7 +23,7 @@
<ProjectReference Include="..\..\Helpers\startup-helpers\startup-helpers.csproj" />
<ProjectReference Include="..\api-models\api-models.csproj" />
<ProjectReference Include="..\common\common.csproj" />
<ProjectReference Include="..\email-api-data\email-api-data.csproj" />
<ProjectReference Include="..\email-data\email-data.csproj" />
<ProjectReference Include="..\email-api-models\email-api-models.csproj" />
</ItemGroup>
</Project>
@@ -1,12 +1,12 @@
using EmailApi.Data.Entities;
using Email.Data.Entities;
using Microsoft.EntityFrameworkCore;
namespace EmailApi.Data;
namespace Email.Data;
public sealed class EmailApiDbContext : DbContext
{
public const string SchemaName = "emailApi";
public const string MigrationTableName = "_Migrations";
public const string SchemaName = MigrationConstants.SchemaName;
public const string MigrationTableName = MigrationConstants.MigrationTableName;
public EmailApiDbContext(DbContextOptions<EmailApiDbContext> options) : base(options) { }
@@ -1,4 +1,4 @@
namespace EmailApi.Data.Entities;
namespace Email.Data.Entities;
// composite PK (Key + Language) — BaseEntity not applicable
public sealed class EmailTemplateEntity
+11
View File
@@ -0,0 +1,11 @@
namespace Email.Data;
/// <summary>
/// Schema constants used by EmailApiDbContext and migrations.
/// Centralized to avoid hardcoded strings and ensure consistency.
/// </summary>
public static class MigrationConstants
{
public const string SchemaName = "email";
public const string MigrationTableName = "_Migrations";
}
@@ -1,6 +1,6 @@
// <auto-generated />
using System;
using EmailApi.Data;
using Email.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
@@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace EmailApi.Data.Migrations
namespace Email.Data.Migrations
{
[DbContext(typeof(EmailApiDbContext))]
[Migration("20260528100000_CreateEmailTemplates")]
@@ -20,13 +20,13 @@ namespace EmailApi.Data.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("emailApi")
.HasDefaultSchema(MigrationConstants.SchemaName)
.HasAnnotation("ProductVersion", "10.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("EmailApi.Data.Entities.EmailTemplateEntity", b =>
modelBuilder.Entity("Email.Data.Entities.EmailTemplateEntity", b =>
{
b.Property<string>("Key")
.HasMaxLength(128)
@@ -61,7 +61,7 @@ namespace EmailApi.Data.Migrations
b.HasKey("Key", "Language");
b.ToTable("EmailTemplates", "emailApi");
b.ToTable("EmailTemplates", MigrationConstants.SchemaName);
});
#pragma warning restore 612, 618
}
@@ -1,9 +1,10 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Email.Data;
#nullable disable
namespace EmailApi.Data.Migrations
namespace Email.Data.Migrations
{
/// <inheritdoc />
public partial class CreateEmailTemplates : Migration
@@ -11,11 +12,11 @@ namespace EmailApi.Data.Migrations
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(name: "emailApi");
migrationBuilder.EnsureSchema(name: MigrationConstants.SchemaName);
migrationBuilder.CreateTable(
name: "EmailTemplates",
schema: "emailApi",
schema: MigrationConstants.SchemaName,
columns: table => new
{
Key = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
@@ -39,7 +40,7 @@ namespace EmailApi.Data.Migrations
=> m.InsertData("EmailTemplates",
["Key", "Language", "Value", "Description", "OperatorCopy"],
[key, lang, value, description, operatorCopy],
"emailApi");
MigrationConstants.SchemaName);
// ── HTML shell (no operator copy — these are layout fragments, not addressable emails) ──
Row("email.html-shell.start", "*",
@@ -1,6 +1,6 @@
// <auto-generated />
using System;
using EmailApi.Data;
using Email.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
@@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace EmailApi.Data.Migrations
namespace Email.Data.Migrations
{
[DbContext(typeof(EmailApiDbContext))]
[Migration("20260528130652_SeedEmailTemplates")]
@@ -20,13 +20,13 @@ namespace EmailApi.Data.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("emailApi")
.HasDefaultSchema(MigrationConstants.SchemaName)
.HasAnnotation("ProductVersion", "10.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("EmailApi.Data.Entities.EmailTemplateEntity", b =>
modelBuilder.Entity("Email.Data.Entities.EmailTemplateEntity", b =>
{
b.Property<string>("Key")
.HasMaxLength(128)
@@ -61,7 +61,7 @@ namespace EmailApi.Data.Migrations
b.HasKey("Key", "Language");
b.ToTable("EmailTemplates", "emailApi");
b.ToTable("EmailTemplates", MigrationConstants.SchemaName);
});
#pragma warning restore 612, 618
}
@@ -1,8 +1,9 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Email.Data;
#nullable disable
namespace EmailApi.Data.Migrations
namespace Email.Data.Migrations
{
/// <inheritdoc />
public partial class SeedEmailTemplates : Migration
@@ -26,12 +27,13 @@ namespace EmailApi.Data.Migrations
private static void Seed(MigrationBuilder m)
{
const string op = "contact@myai.ro";
const string schema = MigrationConstants.SchemaName;
void Row(string key, string lang, string value, string description = "", string operatorCopy = "")
=> m.InsertData("EmailTemplates",
["Key", "Language", "Value", "Description", "OperatorCopy"],
[key, lang, value, description, operatorCopy],
"emailApi");
schema);
// ── HTML shell (no operator copy — these are layout fragments, not addressable emails) ──
Row("email.html-shell.start", "*",
@@ -1,6 +1,6 @@
// <auto-generated />
using System;
using EmailApi.Data;
using Email.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
@@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace EmailApi.Data.Migrations
namespace Email.Data.Migrations
{
[DbContext(typeof(EmailApiDbContext))]
partial class EmailApiDbContextModelSnapshot : ModelSnapshot
@@ -17,13 +17,13 @@ namespace EmailApi.Data.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("emailApi")
.HasDefaultSchema(MigrationConstants.SchemaName)
.HasAnnotation("ProductVersion", "10.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("EmailApi.Data.Entities.EmailTemplateEntity", b =>
modelBuilder.Entity("Email.Data.Entities.EmailTemplateEntity", b =>
{
b.Property<string>("Key")
.HasMaxLength(128)
@@ -58,7 +58,7 @@ namespace EmailApi.Data.Migrations
b.HasKey("Key", "Language");
b.ToTable("EmailTemplates", "emailApi");
b.ToTable("EmailTemplates", MigrationConstants.SchemaName);
});
#pragma warning restore 612, 618
}
@@ -1,6 +1,6 @@
using EmailApi.Data.Entities;
using Email.Data.Entities;
namespace EmailApi.Data.Repositories.Contracts;
namespace Email.Data.Repositories.Contracts;
public interface IEmailTemplateRepository
{
@@ -1,8 +1,8 @@
using EmailApi.Data.Entities;
using EmailApi.Data.Repositories.Contracts;
using Email.Data.Entities;
using Email.Data.Repositories.Contracts;
using Microsoft.EntityFrameworkCore;
namespace EmailApi.Data.Repositories;
namespace Email.Data.Repositories;
public sealed class EfEmailTemplateRepository : IEmailTemplateRepository
{
@@ -1,9 +1,9 @@
using System.Collections.Concurrent;
using EmailApi.Data.Repositories.Contracts;
using Email.Data.Repositories.Contracts;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace EmailApi.Data.Services;
namespace Email.Data.Services;
/// <summary>
/// Singleton implementation of <see cref="IEmailTemplateService"/> that caches all email templates
@@ -1,4 +1,4 @@
namespace EmailApi.Data.Services;
namespace Email.Data.Services;
/// <summary>
/// Provides access to localised email templates stored in the <c>emailApi.EmailTemplates</c> table.
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>email-api-data</AssemblyName>
<RootNamespace>EmailApi.Data</RootNamespace>
<AssemblyName>email-data</AssemblyName>
<RootNamespace>Email.Data</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -13,4 +13,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\shared-data\shared-data.csproj" />
<ProjectReference Include="..\email-api-models\email-api-models.csproj" />
</ItemGroup>
</Project>
+11
View File
@@ -0,0 +1,11 @@
namespace MyAi.Data;
/// <summary>
/// Schema constants used by MyAiDbContext and migrations.
/// Centralized to avoid hardcoded strings and ensure consistency.
/// </summary>
public static class MigrationConstants
{
public const string SchemaName = "myAi";
public const string MigrationTableName = "_Migrations";
}
+2 -2
View File
@@ -5,8 +5,8 @@ namespace MyAi.Data;
public sealed class MyAiDbContext : DbContext
{
public const string SchemaName = "myAi";
public const string MigrationTableName = "_Migrations";
public const string SchemaName = MigrationConstants.SchemaName;
public const string MigrationTableName = MigrationConstants.MigrationTableName;
public MyAiDbContext(DbContextOptions<MyAiDbContext> options) : base(options) { }
@@ -1,5 +1,6 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using MyAi.Data;
#nullable disable
@@ -12,11 +13,11 @@ namespace MyAi.Data.Migrations
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: "myAi");
name: MigrationConstants.SchemaName);
migrationBuilder.CreateTable(
name: "Templates",
schema: "myAi",
schema: MigrationConstants.SchemaName,
columns: table => new
{
Key = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
@@ -36,7 +37,7 @@ namespace MyAi.Data.Migrations
private static void Seed(MigrationBuilder m)
{
void Row(string key, string lang, string value, string description = "")
=> m.InsertData("Templates", ["Key", "Language", "Value", "Description"], [key, lang, value, description], "myAi");
=> m.InsertData("Templates", ["Key", "Language", "Value", "Description"], [key, lang, value, description], MigrationConstants.SchemaName);
// Match result email — subject
Row("email.match.subject", "en", "MyAi.ro CV Match: {{score}}% - {{jobLabel}}", "Subject for the CV match result email");
@@ -107,7 +108,7 @@ namespace MyAi.Data.Migrations
{
migrationBuilder.DropTable(
name: "Templates",
schema: "myAi");
schema: MigrationConstants.SchemaName);
}
}
}
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore.Migrations;
using MyAi.Data;
#nullable disable
@@ -12,7 +13,7 @@ namespace MyAi.Data.Migrations
{
void Update(string key, string lang, string value)
=> migrationBuilder.UpdateData("Templates", ["Key", "Language"], [key, lang],
["Value"], [value], "myAi");
["Value"], [value], MigrationConstants.SchemaName);
// email.match.body — en
Update("email.match.body", "en",
@@ -120,7 +121,7 @@ namespace MyAi.Data.Migrations
{
void Update(string key, string lang, string value)
=> migrationBuilder.UpdateData("Templates", ["Key", "Language"], [key, lang],
["Value"], [value], "myAi");
["Value"], [value], MigrationConstants.SchemaName);
Update("email.match.body", "en",
"CV Matcher result\n\nCV Document ID: {{cvDocumentId}}\nJob: {{jobLabel}}\nJob URL: {{jobUrl}}\nScore: {{score}}%\n\nSummary:\n{{summary}}\n\nStrengths:\n{{strengths}}\n\nGaps:\n{{gaps}}\n\nRecommendations:\n{{recommendations}}");
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore.Migrations;
using MyAi.Data;
#nullable disable
@@ -10,8 +11,8 @@ namespace MyAi.Data.Migrations
/// <inheritdoc />
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.%'");
migrationBuilder.Sql($"DELETE FROM [{MigrationConstants.SchemaName}].[Templates] WHERE [Key] LIKE 'email.%'");
migrationBuilder.Sql($"DELETE FROM [{MigrationConstants.SchemaName}].[Templates] WHERE [Key] LIKE 'ai.%'");
}
/// <inheritdoc />
@@ -1,10 +0,0 @@
namespace MyAi.Models.Data.Entities;
public sealed class TemplateEntity
{
public string Key { get; set; } = string.Empty;
public string Language { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public DateTime UpdatedAt { get; set; }
}
-30
View File
@@ -1,30 +0,0 @@
using MyAi.Models.Data.Entities;
using Microsoft.EntityFrameworkCore;
namespace MyAi.Models.Data;
public sealed class MyAiDbContext : DbContext
{
public const string SchemaName = "myAi";
public const string MigrationTableName = "_MyAiMigrations";
public MyAiDbContext(DbContextOptions<MyAiDbContext> options) : base(options) { }
public DbSet<TemplateEntity> Templates => Set<TemplateEntity>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema(SchemaName);
modelBuilder.Entity<TemplateEntity>(entity =>
{
entity.ToTable("Templates");
entity.HasKey(x => new { x.Key, x.Language });
entity.Property(x => x.Key).HasMaxLength(128);
entity.Property(x => x.Language).HasMaxLength(8);
entity.Property(x => x.Value).IsRequired();
entity.Property(x => x.Description).HasMaxLength(500).HasDefaultValue(string.Empty);
entity.Property(x => x.UpdatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
});
}
}
+11
View File
@@ -0,0 +1,11 @@
namespace Rag.Data;
/// <summary>
/// Schema constants used by RagDbContext and migrations.
/// Centralized to avoid hardcoded strings and ensure consistency.
/// </summary>
public static class MigrationConstants
{
public const string SchemaName = "rag";
public const string MigrationTableName = "_Migrations";
}
@@ -1,5 +1,6 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Rag.Data;
#nullable disable
@@ -12,11 +13,11 @@ namespace Rag.Data.Migrations
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: "rag");
name: MigrationConstants.SchemaName);
migrationBuilder.CreateTable(
name: "ChatCompletionCache",
schema: "rag",
schema: MigrationConstants.SchemaName,
columns: table => new
{
CacheKey = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
@@ -32,7 +33,7 @@ namespace Rag.Data.Migrations
migrationBuilder.CreateTable(
name: "Documents",
schema: "rag",
schema: MigrationConstants.SchemaName,
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
@@ -52,7 +53,7 @@ namespace Rag.Data.Migrations
migrationBuilder.CreateTable(
name: "EmbeddingCache",
schema: "rag",
schema: MigrationConstants.SchemaName,
columns: table => new
{
CacheKey = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
@@ -68,7 +69,7 @@ namespace Rag.Data.Migrations
migrationBuilder.CreateTable(
name: "Chunks",
schema: "rag",
schema: MigrationConstants.SchemaName,
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
@@ -91,25 +92,25 @@ namespace Rag.Data.Migrations
migrationBuilder.CreateIndex(
name: "IX_Chunks_DocumentId",
schema: "rag",
schema: MigrationConstants.SchemaName,
table: "Chunks",
column: "DocumentId");
migrationBuilder.CreateIndex(
name: "IX_Documents_DocumentType",
schema: "rag",
schema: MigrationConstants.SchemaName,
table: "Documents",
column: "DocumentType");
migrationBuilder.CreateIndex(
name: "IX_Documents_TextHash",
schema: "rag",
schema: MigrationConstants.SchemaName,
table: "Documents",
column: "TextHash");
migrationBuilder.CreateIndex(
name: "IX_EmbeddingCache_TextHash",
schema: "rag",
schema: MigrationConstants.SchemaName,
table: "EmbeddingCache",
column: "TextHash");
}
@@ -119,19 +120,19 @@ namespace Rag.Data.Migrations
{
migrationBuilder.DropTable(
name: "ChatCompletionCache",
schema: "rag");
schema: MigrationConstants.SchemaName);
migrationBuilder.DropTable(
name: "Chunks",
schema: "rag");
schema: MigrationConstants.SchemaName);
migrationBuilder.DropTable(
name: "EmbeddingCache",
schema: "rag");
schema: MigrationConstants.SchemaName);
migrationBuilder.DropTable(
name: "Documents",
schema: "rag");
schema: MigrationConstants.SchemaName);
}
}
}
+2 -2
View File
@@ -5,8 +5,8 @@ namespace Rag.Data;
public sealed class RagDbContext : DbContext
{
public const string SchemaName = "rag";
public const string MigrationTableName = "_Migrations";
public const string SchemaName = MigrationConstants.SchemaName;
public const string MigrationTableName = MigrationConstants.MigrationTableName;
public RagDbContext(DbContextOptions<RagDbContext> options) : base(options)
{
@@ -1,6 +1,7 @@
using Rag.Data;
using Rag.Data.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Rag.Data.Repositories.Contracts;
using Rag.Models;
+1
View File
@@ -18,6 +18,7 @@
<ItemGroup>
<ProjectReference Include="..\shared-data\shared-data.csproj" />
<ProjectReference Include="..\rag-api-models\rag-api-models.csproj" />
</ItemGroup>
</Project>
+2 -2
View File
@@ -7,7 +7,7 @@ COPY Jobs/cv-search-job/cv-search-job.csproj Jobs/cv-search-job/
COPY Jobs/job-scheduler/job-scheduler.csproj Jobs/job-scheduler/
COPY Apis/cv-search-data/cv-search-data.csproj Apis/cv-search-data/
COPY Apis/cv-matcher-api-models/cv-matcher-api-models.csproj Apis/cv-matcher-api-models/
COPY Apis/email-api-data/email-api-data.csproj Apis/email-api-data/
COPY Apis/email-data/email-data.csproj Apis/email-data/
COPY Apis/email-api-models/email-api-models.csproj Apis/email-api-models/
COPY Apis/common/common.csproj Apis/common/
COPY Apis/myai-data/myai-data.csproj Apis/myai-data/
@@ -20,7 +20,7 @@ COPY Jobs/cv-search-job/ Jobs/cv-search-job/
COPY Jobs/job-scheduler/ Jobs/job-scheduler/
COPY Apis/cv-search-data/ Apis/cv-search-data/
COPY Apis/cv-matcher-api-models/ Apis/cv-matcher-api-models/
COPY Apis/email-api-data/ Apis/email-api-data/
COPY Apis/email-data/ Apis/email-data/
COPY Apis/email-api-models/ Apis/email-api-models/
COPY Apis/common/ Apis/common/
COPY Apis/myai-data/ Apis/myai-data/
+4 -4
View File
@@ -3,10 +3,10 @@ using CvMatcher.Models.Settings;
using CvSearch.Data;
using CvSearchJob.Clients;
using CvSearchJob.Services;
using EmailApi.Data;
using EmailApi.Data.Repositories;
using EmailApi.Data.Repositories.Contracts;
using EmailApi.Data.Services;
using Email.Data;
using Email.Data.Repositories;
using Email.Data.Repositories.Contracts;
using Email.Data.Services;
using EmailApi.Models.Clients;
using CvSearchJob.Tasks;
using JobScheduler.Scheduling;
@@ -1,6 +1,6 @@
using CvMatcher.Models.Responses;
using CvSearch.Data.Entities;
using EmailApi.Data.Services;
using Email.Data.Services;
using EmailApi.Models.Clients;
using EmailApi.Models.Requests;
using Microsoft.Extensions.Logging;
+1 -1
View File
@@ -21,7 +21,7 @@
<ItemGroup>
<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-data\email-data.csproj" />
<ProjectReference Include="..\..\Apis\email-api-models\email-api-models.csproj" />
<ProjectReference Include="..\..\Apis\cv-search-data\cv-search-data.csproj" />
<ProjectReference Include="..\..\Apis\common\common.csproj" />
+1 -1
View File
@@ -61,7 +61,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "email-api-models", "Apis\em
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "email-api", "Apis\email-api\email-api.csproj", "{434119EA-2FFC-4433-9B8E-1E6D94006413}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "email-api-data", "Apis\email-api-data\email-api-data.csproj", "{C1D2E3F4-A5B6-4789-CDEF-012345678ABC}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "email-data", "Apis\email-data\email-data.csproj", "{C1D2E3F4-A5B6-4789-CDEF-012345678ABC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution