feat(platform): PlatformSettings entity + миграция (singleton SMTP-конфиг)
Платформенные настройки: один row, не tenant-scoped, видны и меняются только Супер-администратором. Хранят SMTP-креды для исходящей почты (forgot-password, нотификации). IMAP к этому отношения не имеет — IMAP для чтения входящей, нам нужен SMTP. Поля: - SmtpHost, SmtpPort - SmtpUseSsl (implicit TLS / 465) и SmtpStartTls (587, по дефолту true) - SmtpUsername, SmtpPasswordEncrypted (хранится зашифрованным через DataProtection API; в API-ответах не выходит, только has-password флаг) - FromEmail, FromName Миграция Phase5b_PlatformSettings создаёт таблицу public.platform_settings. Конфиг EF в AppDbContext: HasMaxLength для строк.
This commit is contained in:
parent
fc9f7c9ee4
commit
1456f170eb
36
src/food-market.domain/Platform/PlatformSettings.cs
Normal file
36
src/food-market.domain/Platform/PlatformSettings.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using foodmarket.Domain.Common;
|
||||
|
||||
namespace foodmarket.Domain.Platform;
|
||||
|
||||
/// <summary>Платформенные настройки (singleton, single-row). Хранят SMTP-креды
|
||||
/// для отправки писем (forgot-password, инвайты, нотификации). Не tenant-scoped —
|
||||
/// общий конфиг для всей платформы, видны и меняются только Супер-администратором.
|
||||
///
|
||||
/// SmtpPassword хранится зашифрованным через DataProtection API
|
||||
/// (`IDataProtectionProvider.CreateProtector("foodmarket.smtp")`); снаружи
|
||||
/// (контроллер) — никогда не возвращается в открытом виде, только has-password флаг.</summary>
|
||||
public class PlatformSettings : Entity
|
||||
{
|
||||
/// <summary>SMTP-сервер для отправки исходящей почты (НЕ IMAP — IMAP это
|
||||
/// для чтения входящей).</summary>
|
||||
public string? SmtpHost { get; set; }
|
||||
public int? SmtpPort { get; set; }
|
||||
|
||||
/// <summary>Implicit TLS (SMTPS, обычно порт 465). Взаимоисключающий
|
||||
/// со SmtpStartTls (587).</summary>
|
||||
public bool SmtpUseSsl { get; set; }
|
||||
|
||||
/// <summary>STARTTLS upgrade (обычно порт 587). По дефолту true в большинстве
|
||||
/// современных провайдеров (Gmail/Yandex/Mailgun).</summary>
|
||||
public bool SmtpStartTls { get; set; } = true;
|
||||
|
||||
public string? SmtpUsername { get; set; }
|
||||
|
||||
/// <summary>Зашифрованный SmtpPassword (base64 через DataProtection).
|
||||
/// Никогда не отдаётся в API-ответах. Установка только через PUT с
|
||||
/// явно переданным new-password полем.</summary>
|
||||
public string? SmtpPasswordEncrypted { get; set; }
|
||||
|
||||
public string? FromEmail { get; set; }
|
||||
public string? FromName { get; set; }
|
||||
}
|
||||
|
|
@ -51,6 +51,7 @@ public AppDbContext(DbContextOptions<AppDbContext> options, ITenantContext tenan
|
|||
public DbSet<EmployeeRetailPointAssignment> EmployeeRetailPointAssignments => Set<EmployeeRetailPointAssignment>();
|
||||
public DbSet<SuperAdminAuditLog> SuperAdminAuditLogs => Set<SuperAdminAuditLog>();
|
||||
public DbSet<SystemSettings> SystemSettings => Set<SystemSettings>();
|
||||
public DbSet<foodmarket.Domain.Platform.PlatformSettings> PlatformSettings => Set<foodmarket.Domain.Platform.PlatformSettings>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
|
|
@ -86,6 +87,15 @@ protected override void OnModelCreating(ModelBuilder builder)
|
|||
b.ToTable("system_settings");
|
||||
});
|
||||
|
||||
builder.Entity<foodmarket.Domain.Platform.PlatformSettings>(b =>
|
||||
{
|
||||
b.ToTable("platform_settings");
|
||||
b.Property(x => x.SmtpHost).HasMaxLength(200);
|
||||
b.Property(x => x.SmtpUsername).HasMaxLength(200);
|
||||
b.Property(x => x.FromEmail).HasMaxLength(200);
|
||||
b.Property(x => x.FromName).HasMaxLength(200);
|
||||
});
|
||||
|
||||
builder.Entity<SuperAdminAuditLog>(b =>
|
||||
{
|
||||
b.ToTable("super_admin_audit_log");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace foodmarket.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <summary>Платформенные настройки (singleton). SMTP-креды для отправки
|
||||
/// писем (forgot-password, нотификации). Управляется SuperAdmin'ом через
|
||||
/// /super-admin/platform-settings. Видна только им.</summary>
|
||||
public partial class Phase5b_PlatformSettings : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder b)
|
||||
{
|
||||
b.CreateTable(
|
||||
name: "platform_settings",
|
||||
schema: "public",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<System.Guid>(type: "uuid", nullable: false),
|
||||
SmtpHost = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
SmtpPort = table.Column<int>(type: "integer", nullable: true),
|
||||
SmtpUseSsl = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
||||
SmtpStartTls = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
|
||||
SmtpUsername = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
SmtpPasswordEncrypted = table.Column<string>(type: "text", nullable: true),
|
||||
FromEmail = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
FromName = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
CreatedAt = table.Column<System.DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<System.DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
},
|
||||
constraints: table => table.PrimaryKey("PK_platform_settings", x => x.Id));
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder b)
|
||||
{
|
||||
b.DropTable(name: "platform_settings", schema: "public");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue