Files
myAi/Apis/email-data/Migrations/20260528100000_CreateEmailTemplates.cs
T
claude 25731868ee fix(email-data): replace hardcoded emailApi schema string with MigrationConstants
Down migration was referencing "emailApi" literal instead of MigrationConstants.SchemaName,
which would have dropped the wrong schema on rollback. Also fix stale comment in DbContext.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 12:19:58 +03:00

192 lines
12 KiB
C#

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 &#8594;</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 &#8594;</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);
}
}
}