Consolidate all migrations into single InitialSchema migrations
Deleted all incremental migrations and regenerated fresh InitialSchema migrations that contain the complete, correct schema from the start: - CvMatcher: InitialSchema with 3-column unique constraint on Results (CvDocumentId, JobDocumentId, Language) - Email: InitialSchema with Templates table (consolidated from EmailTemplates) This creates a cleaner migration history and faster fresh deployments. Since there is no production data, consolidation is safe and improves maintainability. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
-95
@@ -1,95 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using CvMatcher.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CvMatcher.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(CvMatcherDbContext))]
|
||||
[Migration("20260507140442_InitialCvMatcherSchema")]
|
||||
partial class InitialCvMatcherSchema
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("cvMatcher")
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("CvMatcher.Data.Entities.CvMatchResultEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("CvDocumentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("JobDocumentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("ResultJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Score")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CvDocumentId", "JobDocumentId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Results", "cvMatcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CvMatcher.Data.Entities.CvMatcherChatCacheEntity", b =>
|
||||
{
|
||||
b.Property<string>("CacheKey")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("nvarchar(120)");
|
||||
|
||||
b.Property<string>("ResponseText")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<decimal>("Temperature")
|
||||
.HasColumnType("decimal(4,2)");
|
||||
|
||||
b.HasKey("CacheKey");
|
||||
|
||||
b.ToTable("ChatCache", "cvMatcher");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using CvMatcher.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CvMatcher.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(CvMatcherDbContext))]
|
||||
[Migration("20260524140335_AddLanguageToCvMatchResult")]
|
||||
partial class AddLanguageToCvMatchResult
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("cvMatcher")
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("CvMatcher.Data.Entities.CvMatchResultEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("CvDocumentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("JobDocumentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ResultJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Score")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CvDocumentId", "JobDocumentId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Results", "cvMatcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CvMatcher.Data.Entities.CvMatcherChatCacheEntity", b =>
|
||||
{
|
||||
b.Property<string>("CacheKey")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("nvarchar(120)");
|
||||
|
||||
b.Property<string>("ResponseText")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<decimal>("Temperature")
|
||||
.HasColumnType("decimal(4,2)");
|
||||
|
||||
b.HasKey("CacheKey");
|
||||
|
||||
b.ToTable("ChatCache", "cvMatcher");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using CvMatcher.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CvMatcher.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddLanguageToCvMatchResult : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Language",
|
||||
schema: MigrationConstants.SchemaName,
|
||||
table: "Results",
|
||||
type: "nvarchar(max)",
|
||||
nullable: false,
|
||||
defaultValue: "en");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Language",
|
||||
schema: MigrationConstants.SchemaName,
|
||||
table: "Results");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using CvMatcher.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CvMatcher.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(CvMatcherDbContext))]
|
||||
[Migration("20260528110000_AddAiPrompts")]
|
||||
partial class AddAiPrompts
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("cvMatcher")
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("CvMatcher.Data.Entities.AiPromptEntity", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("nvarchar(8)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)")
|
||||
.HasDefaultValue("");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Key", "Language");
|
||||
|
||||
b.ToTable("AiPrompts", "cvMatcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CvMatcher.Data.Entities.CvMatchResultEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("CvDocumentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("JobDocumentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ResultJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Score")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CvDocumentId", "JobDocumentId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Results", "cvMatcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CvMatcher.Data.Entities.CvMatcherChatCacheEntity", b =>
|
||||
{
|
||||
b.Property<string>("CacheKey")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("nvarchar(120)");
|
||||
|
||||
b.Property<string>("ResponseText")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<decimal>("Temperature")
|
||||
.HasColumnType("decimal(4,2)");
|
||||
|
||||
b.HasKey("CacheKey");
|
||||
|
||||
b.ToTable("ChatCache", "cvMatcher");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using CvMatcher.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CvMatcher.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddAiPrompts : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AiPrompts",
|
||||
schema: MigrationConstants.SchemaName,
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
Language = table.Column<string>(type: "nvarchar(8)", maxLength: 8, nullable: false),
|
||||
Value = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Description = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false, defaultValue: ""),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "SYSUTCDATETIME()")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AiPrompts", x => new { x.Key, x.Language });
|
||||
});
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
schema: MigrationConstants.SchemaName,
|
||||
table: "AiPrompts",
|
||||
columns: ["Key", "Language", "Value", "Description"],
|
||||
values: new object[]
|
||||
{
|
||||
"ai.cv-match.system-prompt",
|
||||
"*",
|
||||
"You are a strict CV-to-job matching engine. Return JSON only. Score realistically from 0 to 100.\nPenalize missing required skills. Do not invent experience. Use concise business language.\nRespond entirely in {{languageName}} — all text fields in the JSON must be in {{languageName}}.\nJSON shape: {\"score\":number,\"summary\":\"...\",\"strengths\":[\"...\"],\"gaps\":[\"...\"],\"recommendations\":[\"...\"],\"evidence\":[\"...\"]}",
|
||||
"System prompt template for the CV-to-job LLM matching call. {{languageName}} is substituted at runtime."
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(name: "AiPrompts", schema: MigrationConstants.SchemaName);
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
-130
@@ -1,130 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using CvMatcher.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CvMatcher.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(CvMatcherDbContext))]
|
||||
[Migration("20260529140000_UpdateCvMatchSystemPromptKeywords")]
|
||||
partial class UpdateCvMatchSystemPromptKeywords
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("cvMatcher")
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("CvMatcher.Data.Entities.AiPromptEntity", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("nvarchar(8)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)")
|
||||
.HasDefaultValue("");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Key", "Language");
|
||||
|
||||
b.ToTable("AiPrompts", "cvMatcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CvMatcher.Data.Entities.CvMatchResultEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("CvDocumentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("JobDocumentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ResultJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Score")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CvDocumentId", "JobDocumentId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Results", "cvMatcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CvMatcher.Data.Entities.CvMatcherChatCacheEntity", b =>
|
||||
{
|
||||
b.Property<string>("CacheKey")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("nvarchar(120)");
|
||||
|
||||
b.Property<string>("ResponseText")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<decimal>("Temperature")
|
||||
.HasColumnType("decimal(4,2)");
|
||||
|
||||
b.HasKey("CacheKey");
|
||||
|
||||
b.ToTable("ChatCache", "cvMatcher");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using CvMatcher.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CvMatcher.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class UpdateCvMatchSystemPromptKeywords : Migration
|
||||
{
|
||||
private const string OldPrompt =
|
||||
"You are a strict CV-to-job matching engine. Return JSON only. Score realistically from 0 to 100.\n" +
|
||||
"Penalize missing required skills. Do not invent experience. Use concise business language.\n" +
|
||||
"Respond entirely in {{languageName}} — all text fields in the JSON must be in {{languageName}}.\n" +
|
||||
"JSON shape: {\"score\":number,\"summary\":\"...\",\"strengths\":[\"...\"],\"gaps\":[\"...\"],\"recommendations\":[\"...\"],\"evidence\":[\"...\"]}";
|
||||
|
||||
private const string NewPrompt =
|
||||
"You are a strict CV-to-job matching engine. Return JSON only. Score realistically from 0 to 100.\n" +
|
||||
"Penalize missing required skills. Do not invent experience. Use concise business language.\n" +
|
||||
"Respond entirely in {{languageName}} — all text fields in the JSON must be in {{languageName}}.\n" +
|
||||
"Also extract 8 to 12 English job search keywords from the CV — job titles, technologies, skills, and domains.\n" +
|
||||
"The keywords array must always be in English regardless of {{languageName}}. Exclude names, emails, phone numbers, and locations.\n" +
|
||||
"JSON shape: {\"score\":number,\"summary\":\"...\",\"strengths\":[\"...\"],\"gaps\":[\"...\"],\"recommendations\":[\"...\"],\"evidence\":[\"...\"],\"keywords\":[\"term1\",\"term2\"]}";
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.UpdateData(
|
||||
schema: MigrationConstants.SchemaName,
|
||||
table: "AiPrompts",
|
||||
keyColumns: ["Key", "Language"],
|
||||
keyValues: new object[] { "ai.cv-match.system-prompt", "*" },
|
||||
column: "Value",
|
||||
value: NewPrompt);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.UpdateData(
|
||||
schema: MigrationConstants.SchemaName,
|
||||
table: "AiPrompts",
|
||||
keyColumns: ["Key", "Language"],
|
||||
keyValues: new object[] { "ai.cv-match.system-prompt", "*" },
|
||||
column: "Value",
|
||||
value: OldPrompt);
|
||||
}
|
||||
}
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CvMatcher.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class UpdateResultsUniqueConstraintToIncludeLanguage : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Results_CvDocumentId_JobDocumentId",
|
||||
schema: "cvMatcher",
|
||||
table: "Results");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Language",
|
||||
schema: "cvMatcher",
|
||||
table: "Results",
|
||||
type: "nvarchar(450)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "nvarchar(max)");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Results_CvDocumentId_JobDocumentId_Language",
|
||||
schema: "cvMatcher",
|
||||
table: "Results",
|
||||
columns: new[] { "CvDocumentId", "JobDocumentId", "Language" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Results_CvDocumentId_JobDocumentId_Language",
|
||||
schema: "cvMatcher",
|
||||
table: "Results");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Language",
|
||||
schema: "cvMatcher",
|
||||
table: "Results",
|
||||
type: "nvarchar(max)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "nvarchar(450)");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Results_CvDocumentId_JobDocumentId",
|
||||
schema: "cvMatcher",
|
||||
table: "Results",
|
||||
columns: new[] { "CvDocumentId", "JobDocumentId" },
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -12,8 +12,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
namespace CvMatcher.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(CvMatcherDbContext))]
|
||||
[Migration("20260601131043_UpdateResultsUniqueConstraintToIncludeLanguage")]
|
||||
partial class UpdateResultsUniqueConstraintToIncludeLanguage
|
||||
[Migration("20260601133028_InitialSchema")]
|
||||
partial class InitialSchema
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
+31
-11
@@ -1,23 +1,38 @@
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using CvMatcher.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CvMatcher.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCvMatcherSchema : Migration
|
||||
public partial class InitialSchema : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(
|
||||
name: MigrationConstants.SchemaName);
|
||||
name: "cvMatcher");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AiPrompts",
|
||||
schema: "cvMatcher",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
Language = table.Column<string>(type: "nvarchar(8)", maxLength: 8, nullable: false),
|
||||
Value = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Description = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false, defaultValue: ""),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "SYSUTCDATETIME()")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AiPrompts", x => new { x.Key, x.Language });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ChatCache",
|
||||
schema: MigrationConstants.SchemaName,
|
||||
schema: "cvMatcher",
|
||||
columns: table => new
|
||||
{
|
||||
CacheKey = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
@@ -33,12 +48,13 @@ namespace CvMatcher.Data.Migrations
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Results",
|
||||
schema: MigrationConstants.SchemaName,
|
||||
schema: "cvMatcher",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
CvDocumentId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
JobDocumentId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Language = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
ResultJson = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Score = table.Column<int>(type: "int", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "SYSUTCDATETIME()")
|
||||
@@ -49,23 +65,27 @@ namespace CvMatcher.Data.Migrations
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Results_CvDocumentId_JobDocumentId",
|
||||
schema: MigrationConstants.SchemaName,
|
||||
name: "IX_Results_CvDocumentId_JobDocumentId_Language",
|
||||
schema: "cvMatcher",
|
||||
table: "Results",
|
||||
columns: new[] { "CvDocumentId", "JobDocumentId" },
|
||||
columns: new[] { "CvDocumentId", "JobDocumentId", "Language" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "AiPrompts",
|
||||
schema: "cvMatcher");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ChatCache",
|
||||
schema: MigrationConstants.SchemaName);
|
||||
schema: "cvMatcher");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Results",
|
||||
schema: MigrationConstants.SchemaName);
|
||||
schema: "cvMatcher");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Email.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Email.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(EmailDbContext))]
|
||||
[Migration("20260528100000_CreateEmailTemplates")]
|
||||
partial class CreateEmailTemplates
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema(MigrationConstants.SchemaName)
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Email.Data.Entities.EmailTemplateEntity", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("nvarchar(8)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)")
|
||||
.HasDefaultValue("");
|
||||
|
||||
b.Property<string>("OperatorCopy")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)")
|
||||
.HasDefaultValue("");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Key", "Language");
|
||||
|
||||
b.ToTable("EmailTemplates", MigrationConstants.SchemaName);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Email.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Email.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class CreateEmailTemplates : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(name: MigrationConstants.SchemaName);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "EmailTemplates",
|
||||
schema: MigrationConstants.SchemaName,
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
Language = table.Column<string>(type: "nvarchar(8)", maxLength: 8, nullable: false),
|
||||
Value = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Description = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false, defaultValue: ""),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "SYSUTCDATETIME()"),
|
||||
OperatorCopy = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false, defaultValue: "")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_EmailTemplates", x => new { x.Key, x.Language });
|
||||
});
|
||||
}
|
||||
|
||||
private static void Seed(MigrationBuilder m)
|
||||
{
|
||||
const string op = "contact@myai.ro";
|
||||
|
||||
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],
|
||||
MigrationConstants.SchemaName);
|
||||
|
||||
// ── HTML shell (no operator copy — these are layout fragments, not addressable emails) ──
|
||||
Row("email.html-shell.start", "*",
|
||||
"<!DOCTYPE html>\n<html>\n<head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"></head>\n<body style=\"margin:0;padding:0;background:#f4f4f4;font-family:Arial,Helvetica,sans-serif\">\n <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"padding:20px 0\">\n <tr><td align=\"center\">\n <table width=\"600\" cellpadding=\"0\" cellspacing=\"0\"\n style=\"background:#ffffff;border-radius:8px;max-width:600px\">\n <tr><td style=\"background:#2c5282;padding:24px 32px;border-radius:8px 8px 0 0\">\n <h1 style=\"margin:0;color:#ffffff;font-size:22px;font-weight:600\">myAi</h1>\n </td></tr>\n <tr><td style=\"padding:32px\">",
|
||||
"Opening HTML shell fragment — wrapped around every HtmlBody before sending");
|
||||
|
||||
Row("email.html-shell.end", "*",
|
||||
" </td></tr>\n <tr><td style=\"background:#f8f9fa;padding:16px 32px;text-align:center;\n color:#6c757d;font-size:12px;border-radius:0 0 8px 8px\">\n Automated message from myAi.\n </td></tr>\n </table>\n </td></tr>\n </table>\n</body>\n</html>",
|
||||
"Closing HTML shell fragment — appended after every HtmlBody before sending");
|
||||
|
||||
// ── CV match result email ──
|
||||
Row("email.match.subject", "en",
|
||||
"MyAi.ro CV Match: {{score}}% - {{jobLabel}}",
|
||||
"Subject for the CV match result email",
|
||||
op);
|
||||
|
||||
Row("email.match.subject", "ro",
|
||||
"MyAi.ro Potrivire CV: {{score}}% - {{jobLabel}}",
|
||||
"Subiect email rezultat potrivire CV",
|
||||
op);
|
||||
|
||||
Row("email.match.body", "en",
|
||||
"<h2 style=\"margin:0 0 20px;color:#2c5282;font-size:20px\">CV Match Report</h2>" +
|
||||
"<table cellpadding=\"10\" cellspacing=\"0\" style=\"width:100%;border-collapse:collapse;margin-bottom:24px\">" +
|
||||
"<tr style=\"background:#f8f9fa\">" +
|
||||
"<td style=\"font-weight:600;width:130px;border:1px solid #dee2e6;color:#495057\">CV ID</td>" +
|
||||
"<td style=\"border:1px solid #dee2e6;font-family:monospace;font-size:13px\">{{cvDocumentId}}</td>" +
|
||||
"</tr>" +
|
||||
"<tr>" +
|
||||
"<td style=\"font-weight:600;border:1px solid #dee2e6;color:#495057\">Job</td>" +
|
||||
"<td style=\"border:1px solid #dee2e6\">{{jobLabel}}</td>" +
|
||||
"</tr>" +
|
||||
"<tr style=\"background:#f8f9fa\">" +
|
||||
"<td style=\"font-weight:600;border:1px solid #dee2e6;color:#495057\">URL</td>" +
|
||||
"<td style=\"border:1px solid #dee2e6\"><a href=\"{{jobUrl}}\" style=\"color:#2c5282\">{{jobUrl}}</a></td>" +
|
||||
"</tr>" +
|
||||
"<tr>" +
|
||||
"<td style=\"font-weight:600;border:1px solid #dee2e6;color:#495057\">Score</td>" +
|
||||
"<td style=\"border:1px solid #dee2e6;font-size:26px;font-weight:700;color:#28a745\">{{score}}%</td>" +
|
||||
"</tr>" +
|
||||
"</table>" +
|
||||
"<h3 style=\"color:#2c5282;border-bottom:2px solid #e9ecef;padding-bottom:6px\">Summary</h3>" +
|
||||
"<p style=\"color:#495057;line-height:1.7\">{{summary}}</p>" +
|
||||
"<h3 style=\"color:#2c5282;border-bottom:2px solid #e9ecef;padding-bottom:6px\">Strengths</h3>{{strengths}}" +
|
||||
"<h3 style=\"color:#2c5282;border-bottom:2px solid #e9ecef;padding-bottom:6px\">Gaps</h3>{{gaps}}" +
|
||||
"<h3 style=\"color:#2c5282;border-bottom:2px solid #e9ecef;padding-bottom:6px\">Recommendations</h3>{{recommendations}}",
|
||||
"Body for the CV match result email",
|
||||
op);
|
||||
|
||||
Row("email.match.body", "ro",
|
||||
"<h2 style=\"margin:0 0 20px;color:#2c5282;font-size:20px\">Raport Potrivire CV</h2>" +
|
||||
"<table cellpadding=\"10\" cellspacing=\"0\" style=\"width:100%;border-collapse:collapse;margin-bottom:24px\">" +
|
||||
"<tr style=\"background:#f8f9fa\">" +
|
||||
"<td style=\"font-weight:600;width:130px;border:1px solid #dee2e6;color:#495057\">ID Document CV</td>" +
|
||||
"<td style=\"border:1px solid #dee2e6;font-family:monospace;font-size:13px\">{{cvDocumentId}}</td>" +
|
||||
"</tr>" +
|
||||
"<tr>" +
|
||||
"<td style=\"font-weight:600;border:1px solid #dee2e6;color:#495057\">Job</td>" +
|
||||
"<td style=\"border:1px solid #dee2e6\">{{jobLabel}}</td>" +
|
||||
"</tr>" +
|
||||
"<tr style=\"background:#f8f9fa\">" +
|
||||
"<td style=\"font-weight:600;border:1px solid #dee2e6;color:#495057\">URL</td>" +
|
||||
"<td style=\"border:1px solid #dee2e6\"><a href=\"{{jobUrl}}\" style=\"color:#2c5282\">{{jobUrl}}</a></td>" +
|
||||
"</tr>" +
|
||||
"<tr>" +
|
||||
"<td style=\"font-weight:600;border:1px solid #dee2e6;color:#495057\">Scor</td>" +
|
||||
"<td style=\"border:1px solid #dee2e6;font-size:26px;font-weight:700;color:#28a745\">{{score}}%</td>" +
|
||||
"</tr>" +
|
||||
"</table>" +
|
||||
"<h3 style=\"color:#2c5282;border-bottom:2px solid #e9ecef;padding-bottom:6px\">Rezumat</h3>" +
|
||||
"<p style=\"color:#495057;line-height:1.7\">{{summary}}</p>" +
|
||||
"<h3 style=\"color:#2c5282;border-bottom:2px solid #e9ecef;padding-bottom:6px\">Puncte forte</h3>{{strengths}}" +
|
||||
"<h3 style=\"color:#2c5282;border-bottom:2px solid #e9ecef;padding-bottom:6px\">Lipsuri</h3>{{gaps}}" +
|
||||
"<h3 style=\"color:#2c5282;border-bottom:2px solid #e9ecef;padding-bottom:6px\">Recomandări</h3>{{recommendations}}",
|
||||
"Corpul emailului pentru rezultatul potrivirii CV",
|
||||
op);
|
||||
|
||||
Row("email.match.job-search-footer", "en",
|
||||
"<div style=\"background:#f0f4f8;border-left:4px solid #2c5282;padding:16px;margin-top:24px;border-radius:4px\">" +
|
||||
"<p style=\"margin:0;color:#495057\">" +
|
||||
"Want to find matching jobs automatically? " +
|
||||
"<a href=\"{{jobSearchLink}}\" style=\"color:#2c5282;font-weight:600\">Start a job search →</a><br>" +
|
||||
"<small style=\"color:#6c757d\">Link valid for {{expiryDays}} days.</small>" +
|
||||
"</p>" +
|
||||
"</div>",
|
||||
"Job search CTA appended to match result email",
|
||||
op);
|
||||
|
||||
Row("email.match.job-search-footer", "ro",
|
||||
"<div style=\"background:#f0f4f8;border-left:4px solid #2c5282;padding:16px;margin-top:24px;border-radius:4px\">" +
|
||||
"<p style=\"margin:0;color:#495057\">" +
|
||||
"Vrei să găsești joburi potrivite automat? " +
|
||||
"<a href=\"{{jobSearchLink}}\" style=\"color:#2c5282;font-weight:600\">Pornește o căutare de joburi →</a><br>" +
|
||||
"<small style=\"color:#6c757d\">Link valabil {{expiryDays}} zile.</small>" +
|
||||
"</p>" +
|
||||
"</div>",
|
||||
"CTA cautare joburi adaugat la emailul de potrivire CV",
|
||||
op);
|
||||
|
||||
// ── Job search results email ──
|
||||
Row("email.search-results.subject", "en",
|
||||
"MyAi.ro: {{count}} jobs matching your CV",
|
||||
"Subject for job search results email",
|
||||
op);
|
||||
|
||||
Row("email.search-results.subject", "ro",
|
||||
"MyAi.ro: {{count}} joburi potrivite CV-ului tau",
|
||||
"Subiect email rezultate cautare joburi",
|
||||
op);
|
||||
|
||||
Row("email.search-results.body", "en",
|
||||
"<h2 style=\"margin:0 0 16px;color:#2c5282\">Job Search Results</h2>" +
|
||||
"<p style=\"color:#495057\">Found <strong>{{count}}</strong> matching job(s):</p>" +
|
||||
"{{items}}",
|
||||
"Body preamble for job search results email",
|
||||
op);
|
||||
|
||||
Row("email.search-results.body", "ro",
|
||||
"<h2 style=\"margin:0 0 16px;color:#2c5282\">Rezultate Căutare Joburi</h2>" +
|
||||
"<p style=\"color:#495057\">Am găsit <strong>{{count}}</strong> job(uri) potrivite:</p>" +
|
||||
"{{items}}",
|
||||
"Corpul emailului de rezultate cautare joburi",
|
||||
op);
|
||||
|
||||
Row("email.search-results.empty", "en",
|
||||
"<div style=\"text-align:center;padding:32px;color:#6c757d\">" +
|
||||
"<p style=\"font-size:18px;margin:0 0 8px;color:#495057\">No matching jobs found</p>" +
|
||||
"<p style=\"margin:0\">Your job search completed but no matching jobs were found. Try again later or adjust your CV.</p>" +
|
||||
"</div>",
|
||||
"No results message for job search results email",
|
||||
op);
|
||||
|
||||
Row("email.search-results.empty", "ro",
|
||||
"<div style=\"text-align:center;padding:32px;color:#6c757d\">" +
|
||||
"<p style=\"font-size:18px;margin:0 0 8px;color:#495057\">Niciun job potrivit găsit</p>" +
|
||||
"<p style=\"margin:0\">Căutarea s-a finalizat dar nu au fost găsite joburi potrivite. Încearcă mai târziu sau ajustează CV-ul.</p>" +
|
||||
"</div>",
|
||||
"Mesaj fara rezultate pentru emailul de cautare joburi",
|
||||
op);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(name: "EmailTemplates", schema: MigrationConstants.SchemaName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Email.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Email.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(EmailDbContext))]
|
||||
[Migration("20260528130652_SeedEmailTemplates")]
|
||||
partial class SeedEmailTemplates
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema(MigrationConstants.SchemaName)
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Email.Data.Entities.EmailTemplateEntity", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("nvarchar(8)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)")
|
||||
.HasDefaultValue("");
|
||||
|
||||
b.Property<string>("OperatorCopy")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)")
|
||||
.HasDefaultValue("");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Key", "Language");
|
||||
|
||||
b.ToTable("EmailTemplates", MigrationConstants.SchemaName);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Email.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Email.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SeedEmailTemplates : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
Seed(migrationBuilder);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Delete all seeded templates (only those we know we added)
|
||||
migrationBuilder.DeleteData(
|
||||
table: "EmailTemplates",
|
||||
keyColumns: new[] { "Key", "Language" },
|
||||
keyValues: new object[] { "email.html-shell.start", "*" });
|
||||
}
|
||||
|
||||
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],
|
||||
schema);
|
||||
|
||||
// ── HTML shell (no operator copy — these are layout fragments, not addressable emails) ──
|
||||
Row("email.html-shell.start", "*",
|
||||
"<!DOCTYPE html>\n<html>\n<head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"></head>\n<body style=\"margin:0;padding:0;background:#f4f4f4;font-family:Arial,Helvetica,sans-serif\">\n <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"padding:20px 0\">\n <tr><td align=\"center\">\n <table width=\"600\" cellpadding=\"0\" cellspacing=\"0\"\n style=\"background:#ffffff;border-radius:8px;max-width:600px\">\n <tr><td style=\"background:#2c5282;padding:24px 32px;border-radius:8px 8px 0 0\">\n <h1 style=\"margin:0;color:#ffffff;font-size:22px;font-weight:600\">myAi</h1>\n </td></tr>\n <tr><td style=\"padding:32px\">",
|
||||
"Opening HTML shell fragment — wrapped around every HtmlBody before sending");
|
||||
|
||||
Row("email.html-shell.end", "*",
|
||||
" </td></tr>\n <tr><td style=\"background:#f8f9fa;padding:16px 32px;text-align:center;\n color:#6c757d;font-size:12px;border-radius:0 0 8px 8px\">\n Automated message from myAi.\n </td></tr>\n </table>\n </td></tr>\n </table>\n</body>\n</html>",
|
||||
"Closing HTML shell fragment — appended after every HtmlBody before sending");
|
||||
|
||||
// ── CV match result email ──
|
||||
Row("email.match.subject", "en",
|
||||
"MyAi.ro CV Match: {{score}}% - {{jobLabel}}",
|
||||
"Subject for the CV match result email",
|
||||
op);
|
||||
|
||||
Row("email.match.subject", "ro",
|
||||
"MyAi.ro Potrivire CV: {{score}}% - {{jobLabel}}",
|
||||
"Subiect email rezultat potrivire CV",
|
||||
op);
|
||||
|
||||
Row("email.match.body", "en",
|
||||
"<h2 style=\"margin:0 0 20px;color:#2c5282;font-size:20px\">CV Match Report</h2>" +
|
||||
"<table cellpadding=\"10\" cellspacing=\"0\" style=\"width:100%;border-collapse:collapse;margin-bottom:24px\">" +
|
||||
"<tr style=\"background:#f8f9fa\">" +
|
||||
"<td style=\"font-weight:600;width:130px;border:1px solid #dee2e6;color:#495057\">CV ID</td>" +
|
||||
"<td style=\"border:1px solid #dee2e6;font-family:monospace;font-size:13px\">{{cvDocumentId}}</td>" +
|
||||
"</tr>" +
|
||||
"<tr>" +
|
||||
"<td style=\"font-weight:600;border:1px solid #dee2e6;color:#495057\">Job</td>" +
|
||||
"<td style=\"border:1px solid #dee2e6\">{{jobLabel}}</td>" +
|
||||
"</tr>" +
|
||||
"<tr style=\"background:#f8f9fa\">" +
|
||||
"<td style=\"font-weight:600;border:1px solid #dee2e6;color:#495057\">URL</td>" +
|
||||
"<td style=\"border:1px solid #dee2e6\"><a href=\"{{jobUrl}}\" style=\"color:#2c5282\">{{jobUrl}}</a></td>" +
|
||||
"</tr>" +
|
||||
"<tr>" +
|
||||
"<td style=\"font-weight:600;border:1px solid #dee2e6;color:#495057\">Score</td>" +
|
||||
"<td style=\"border:1px solid #dee2e6;font-size:26px;font-weight:700;color:#28a745\">{{score}}%</td>" +
|
||||
"</tr>" +
|
||||
"</table>" +
|
||||
"<h3 style=\"color:#2c5282;border-bottom:2px solid #e9ecef;padding-bottom:6px\">Summary</h3>" +
|
||||
"<p style=\"color:#495057;line-height:1.7\">{{summary}}</p>" +
|
||||
"<h3 style=\"color:#2c5282;border-bottom:2px solid #e9ecef;padding-bottom:6px\">Strengths</h3>{{strengths}}" +
|
||||
"<h3 style=\"color:#2c5282;border-bottom:2px solid #e9ecef;padding-bottom:6px\">Gaps</h3>{{gaps}}" +
|
||||
"<h3 style=\"color:#2c5282;border-bottom:2px solid #e9ecef;padding-bottom:6px\">Recommendations</h3>{{recommendations}}",
|
||||
"Body for the CV match result email",
|
||||
op);
|
||||
|
||||
Row("email.match.body", "ro",
|
||||
"<h2 style=\"margin:0 0 20px;color:#2c5282;font-size:20px\">Raport Potrivire CV</h2>" +
|
||||
"<table cellpadding=\"10\" cellspacing=\"0\" style=\"width:100%;border-collapse:collapse;margin-bottom:24px\">" +
|
||||
"<tr style=\"background:#f8f9fa\">" +
|
||||
"<td style=\"font-weight:600;width:130px;border:1px solid #dee2e6;color:#495057\">ID Document CV</td>" +
|
||||
"<td style=\"border:1px solid #dee2e6;font-family:monospace;font-size:13px\">{{cvDocumentId}}</td>" +
|
||||
"</tr>" +
|
||||
"<tr>" +
|
||||
"<td style=\"font-weight:600;border:1px solid #dee2e6;color:#495057\">Job</td>" +
|
||||
"<td style=\"border:1px solid #dee2e6\">{{jobLabel}}</td>" +
|
||||
"</tr>" +
|
||||
"<tr style=\"background:#f8f9fa\">" +
|
||||
"<td style=\"font-weight:600;border:1px solid #dee2e6;color:#495057\">URL</td>" +
|
||||
"<td style=\"border:1px solid #dee2e6\"><a href=\"{{jobUrl}}\" style=\"color:#2c5282\">{{jobUrl}}</a></td>" +
|
||||
"</tr>" +
|
||||
"<tr>" +
|
||||
"<td style=\"font-weight:600;border:1px solid #dee2e6;color:#495057\">Scor</td>" +
|
||||
"<td style=\"border:1px solid #dee2e6;font-size:26px;font-weight:700;color:#28a745\">{{score}}%</td>" +
|
||||
"</tr>" +
|
||||
"</table>" +
|
||||
"<h3 style=\"color:#2c5282;border-bottom:2px solid #e9ecef;padding-bottom:6px\">Rezumat</h3>" +
|
||||
"<p style=\"color:#495057;line-height:1.7\">{{summary}}</p>" +
|
||||
"<h3 style=\"color:#2c5282;border-bottom:2px solid #e9ecef;padding-bottom:6px\">Puncte forte</h3>{{strengths}}" +
|
||||
"<h3 style=\"color:#2c5282;border-bottom:2px solid #e9ecef;padding-bottom:6px\">Lipsuri</h3>{{gaps}}" +
|
||||
"<h3 style=\"color:#2c5282;border-bottom:2px solid #e9ecef;padding-bottom:6px\">Recomandări</h3>{{recommendations}}",
|
||||
"Corpul emailului pentru rezultatul potrivirii CV",
|
||||
op);
|
||||
|
||||
Row("email.match.job-search-footer", "en",
|
||||
"<div style=\"background:#f0f4f8;border-left:4px solid #2c5282;padding:16px;margin-top:24px;border-radius:4px\">" +
|
||||
"<p style=\"margin:0;color:#495057\">" +
|
||||
"Want to find matching jobs automatically? " +
|
||||
"<a href=\"{{jobSearchLink}}\" style=\"color:#2c5282;font-weight:600\">Start a job search →</a><br>" +
|
||||
"<small style=\"color:#6c757d\">Link valid for {{expiryDays}} days.</small>" +
|
||||
"</p>" +
|
||||
"</div>",
|
||||
"Job search CTA appended to match result email",
|
||||
op);
|
||||
|
||||
Row("email.match.job-search-footer", "ro",
|
||||
"<div style=\"background:#f0f4f8;border-left:4px solid #2c5282;padding:16px;margin-top:24px;border-radius:4px\">" +
|
||||
"<p style=\"margin:0;color:#495057\">" +
|
||||
"Vrei să găsești joburi potrivite automat? " +
|
||||
"<a href=\"{{jobSearchLink}}\" style=\"color:#2c5282;font-weight:600\">Pornește o căutare de joburi →</a><br>" +
|
||||
"<small style=\"color:#6c757d\">Link valabil {{expiryDays}} zile.</small>" +
|
||||
"</p>" +
|
||||
"</div>",
|
||||
"CTA cautare joburi adaugat la emailul de potrivire CV",
|
||||
op);
|
||||
|
||||
// ── Job search results email ──
|
||||
Row("email.search-results.subject", "en",
|
||||
"MyAi.ro: {{count}} jobs matching your CV",
|
||||
"Subject for job search results email",
|
||||
op);
|
||||
|
||||
Row("email.search-results.subject", "ro",
|
||||
"MyAi.ro: {{count}} joburi potrivite CV-ului tau",
|
||||
"Subiect email rezultate cautare joburi",
|
||||
op);
|
||||
|
||||
Row("email.search-results.body", "en",
|
||||
"<h2 style=\"margin:0 0 16px;color:#2c5282\">Job Search Results</h2>" +
|
||||
"<p style=\"color:#495057\">Found <strong>{{count}}</strong> matching job(s):</p>" +
|
||||
"{{items}}",
|
||||
"Body preamble for job search results email",
|
||||
op);
|
||||
|
||||
Row("email.search-results.body", "ro",
|
||||
"<h2 style=\"margin:0 0 16px;color:#2c5282\">Rezultate Căutare Joburi</h2>" +
|
||||
"<p style=\"color:#495057\">Am găsit <strong>{{count}}</strong> job(uri) potrivite:</p>" +
|
||||
"{{items}}",
|
||||
"Corpul emailului de rezultate cautare joburi",
|
||||
op);
|
||||
|
||||
Row("email.search-results.empty", "en",
|
||||
"<div style=\"text-align:center;padding:32px;color:#6c757d\">" +
|
||||
"<p style=\"font-size:18px;margin:0 0 8px;color:#495057\">No matching jobs found</p>" +
|
||||
"<p style=\"margin:0\">Your job search completed but no matching jobs were found. Try again later or adjust your CV.</p>" +
|
||||
"</div>",
|
||||
"No results message for job search results email",
|
||||
op);
|
||||
|
||||
Row("email.search-results.empty", "ro",
|
||||
"<div style=\"text-align:center;padding:32px;color:#6c757d\">" +
|
||||
"<p style=\"font-size:18px;margin:0 0 8px;color:#495057\">Niciun job potrivit găsit</p>" +
|
||||
"<p style=\"margin:0\">Căutarea s-a finalizat dar nu au fost găsite joburi potrivite. Încearcă mai târziu sau ajustează CV-ul.</p>" +
|
||||
"</div>",
|
||||
"Mesaj fara rezultate pentru emailul de cautare joburi",
|
||||
op);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Email.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RenameEmailTemplatesToTemplates : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameTable(
|
||||
name: "EmailTemplates",
|
||||
schema: MigrationConstants.SchemaName,
|
||||
newName: "Templates");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameTable(
|
||||
name: "Templates",
|
||||
schema: MigrationConstants.SchemaName,
|
||||
newName: "EmailTemplates");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -12,8 +12,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
namespace Email.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(EmailDbContext))]
|
||||
[Migration("20260601132154_RenameEmailTemplatesToTemplates")]
|
||||
partial class RenameEmailTemplatesToTemplates
|
||||
[Migration("20260601133043_InitialSchema")]
|
||||
partial class InitialSchema
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Email.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialSchema : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(
|
||||
name: "email");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Templates",
|
||||
schema: "email",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
Language = table.Column<string>(type: "nvarchar(8)", maxLength: 8, nullable: false),
|
||||
Value = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Description = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false, defaultValue: ""),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "SYSUTCDATETIME()"),
|
||||
OperatorCopy = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false, defaultValue: "")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Templates", x => new { x.Key, x.Language });
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Templates",
|
||||
schema: "email");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ namespace Email.Data.Migrations
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema(MigrationConstants.SchemaName)
|
||||
.HasDefaultSchema("email")
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Email.Data.Migrations
|
||||
|
||||
b.HasKey("Key", "Language");
|
||||
|
||||
b.ToTable("Templates", MigrationConstants.SchemaName);
|
||||
b.ToTable("Templates", "email");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user