feat: add UpdateEmailTemplatesToHtml migration
Upgrades 8 email body templates from plain text to styled HTML. Templates: email.match.body, email.match.job-search-footer, email.search-results.body, email.search-results.empty (en + ro each). All use inline CSS only (Gmail-compatible). Branded #2c5282 accent. Closes #22 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+62
@@ -0,0 +1,62 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using MyAi.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MyAi.Data.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(MyAiDbContext))]
|
||||||
|
[Migration("20260527120000_UpdateEmailTemplatesToHtml")]
|
||||||
|
partial class UpdateEmailTemplatesToHtml
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasDefaultSchema("myAi")
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("MyAi.Data.Entities.TemplateEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<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("Templates", "myAi");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MyAi.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class UpdateEmailTemplatesToHtml : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
void Update(string key, string lang, string value)
|
||||||
|
=> migrationBuilder.UpdateData("Templates", ["Key", "Language"], [key, lang],
|
||||||
|
["Value"], [value], "myAi");
|
||||||
|
|
||||||
|
// email.match.body — en
|
||||||
|
Update("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}}");
|
||||||
|
|
||||||
|
// email.match.body — ro
|
||||||
|
Update("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}}");
|
||||||
|
|
||||||
|
// email.match.job-search-footer — en
|
||||||
|
Update("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>");
|
||||||
|
|
||||||
|
// email.match.job-search-footer — ro
|
||||||
|
Update("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>");
|
||||||
|
|
||||||
|
// email.search-results.body — en
|
||||||
|
Update("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}}");
|
||||||
|
|
||||||
|
// email.search-results.body — ro
|
||||||
|
Update("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}}");
|
||||||
|
|
||||||
|
// email.search-results.empty — en
|
||||||
|
Update("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>");
|
||||||
|
|
||||||
|
// email.search-results.empty — ro
|
||||||
|
Update("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>");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
void Update(string key, string lang, string value)
|
||||||
|
=> migrationBuilder.UpdateData("Templates", ["Key", "Language"], [key, lang],
|
||||||
|
["Value"], [value], "myAi");
|
||||||
|
|
||||||
|
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}}");
|
||||||
|
Update("email.match.body", "ro",
|
||||||
|
"Rezultat potrivire CV\n\nID document CV: {{cvDocumentId}}\nJob: {{jobLabel}}\nURL job: {{jobUrl}}\nScor: {{score}}%\n\nRezumat:\n{{summary}}\n\nPuncte forte:\n{{strengths}}\n\nLipsuri:\n{{gaps}}\n\nRecomandări:\n{{recommendations}}");
|
||||||
|
Update("email.match.job-search-footer", "en",
|
||||||
|
"\n\n---\nWant to find more jobs matching your CV?\nClick: {{jobSearchLink}}\n(link valid for {{expiryDays}} days)");
|
||||||
|
Update("email.match.job-search-footer", "ro",
|
||||||
|
"\n\n---\nVrei sa gasesti mai multe joburi potrivite CV-ului tau?\nClick: {{jobSearchLink}}\n(link valabil {{expiryDays}} zile)");
|
||||||
|
Update("email.search-results.body", "en",
|
||||||
|
"MyAi.ro found {{count}} jobs matching your CV:\n\n{{items}}");
|
||||||
|
Update("email.search-results.body", "ro",
|
||||||
|
"MyAi.ro a gasit {{count}} joburi potrivite CV-ului tau:\n\n{{items}}");
|
||||||
|
Update("email.search-results.empty", "en",
|
||||||
|
"MyAi.ro found no jobs matching your CV. Try again later or update your CV.");
|
||||||
|
Update("email.search-results.empty", "ro",
|
||||||
|
"MyAi.ro nu a gasit joburi care sa corespunda CV-ului tau. Incercati mai tarziu sau ajustati CV-ul.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user