Generalize mail templates from a fixed pair to an operator-managed list
ArchiveKind was a hardcoded two-value enum (ssh_access/config_update),
wired through FilenameParser's regex, MailTemplateSet's two fixed
slots, and SettingsForm's two fixed tabs. Operators wanted arbitrary
custom templates (own file-name suffix, subject, body) without a code
change per new archive kind ssh-access-tui might start producing.
Replaced with MailTemplateDefinition (DisplayName/FileSuffix/Subject/
BodyHtml) in an open-ended list: FilenameParser now accepts any
"{login}_{suffix}.7z" and ArchiveScanner matches the suffix against
the configured list; SendJob.Kind/SendLogEntry.Kind became plain
strings; SettingsForm gained a template list (+/- buttons, editor
panel) replacing the two static tabs. ssh_access/config_update are now
just the two default entries in that list, not special-cased.
Handles the two backward-compat edges that matter for a tool already
in real use: old config.json's object-shaped "templates" migrates to
the new list shape transparently on first load (DataContractJsonSerializer
turned out to silently produce an empty list rather than throw on the
old shape, so detection is structural, not exception-based), and old
send-log.csv rows with PascalCase Kind values (SshAccess/ConfigUpdate)
still dedupe correctly against the new snake_case suffixes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cb3b273741
commit
804b9610ca
16 files changed
+904
-288
No files matched your search
@@ -22,7 +22,10 @@ namespace BulkArchiveMailer.Core
|
||||
public DateTime TimestampUtc { get; set; }
|
||||
public string Login { get; set; } = "";
|
||||
public string Email { get; set; } = "";
|
||||
public ArchiveKind Kind { get; set; }
|
||||
|
||||
/// <summary>File suffix ("ssh_access", "config_update", or any other configured template's FileSuffix) - see the normalization note on SendLogStore.ReadAll for the legacy PascalCase values (e.g. "SshAccess") written by versions v1.0.0-v1.0.4.</summary>
|
||||
public string Kind { get; set; } = "";
|
||||
|
||||
public string ArchivePath { get; set; } = "";
|
||||
public string FileSha256 { get; set; } = "";
|
||||
public string Status { get; set; } = "";
|
||||
@@ -109,8 +112,7 @@ namespace BulkArchiveMailer.Core
|
||||
continue; // malformed/truncated row - skip rather than throw
|
||||
}
|
||||
|
||||
ArchiveKind kind;
|
||||
Enum.TryParse(fields[3], true, out kind);
|
||||
var kind = NormalizeLegacyKind(fields[3]);
|
||||
|
||||
DateTime ts;
|
||||
DateTime.TryParse(
|
||||
@@ -136,6 +138,32 @@ namespace BulkArchiveMailer.Core
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps the legacy PascalCase Kind values written by v1.0.0-v1.0.4
|
||||
/// (ArchiveKind.ToString(): "SshAccess"/"ConfigUpdate") to the
|
||||
/// snake_case FileSuffix text used everywhere since ArchiveKind was
|
||||
/// replaced by a free-form template list ("ssh_access"/
|
||||
/// "config_update" - same text as config.default.json's
|
||||
/// "fileSuffix"). Anything else (already-current suffix, or a
|
||||
/// custom suffix that never existed under the old enum) passes
|
||||
/// through unchanged, so TryFindSent's dedup lookup keeps matching
|
||||
/// against send-log.csv rows written before this migration.
|
||||
/// </summary>
|
||||
private static string NormalizeLegacyKind(string rawKind)
|
||||
{
|
||||
if (string.Equals(rawKind, "SshAccess", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "ssh_access";
|
||||
}
|
||||
|
||||
if (string.Equals(rawKind, "ConfigUpdate", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "config_update";
|
||||
}
|
||||
|
||||
return rawKind;
|
||||
}
|
||||
|
||||
public void Append(SendLogEntry entry)
|
||||
{
|
||||
if (entry == null)
|
||||
@@ -162,7 +190,7 @@ namespace BulkArchiveMailer.Core
|
||||
Escape(entry.TimestampUtc.ToString("o", CultureInfo.InvariantCulture)),
|
||||
Escape(entry.Login),
|
||||
Escape(entry.Email),
|
||||
Escape(entry.Kind.ToString()),
|
||||
Escape(entry.Kind ?? ""),
|
||||
Escape(entry.ArchivePath),
|
||||
Escape(entry.FileSha256),
|
||||
Escape(entry.Status),
|
||||
@@ -176,7 +204,7 @@ namespace BulkArchiveMailer.Core
|
||||
/// has Status=Sent. Draft/Failed rows never count as "already
|
||||
/// sent" - only a completed real Send does.
|
||||
/// </summary>
|
||||
public bool TryFindSent(string email, ArchiveKind kind, string sha256, out SendLogEntry match)
|
||||
public bool TryFindSent(string email, string kind, string sha256, out SendLogEntry match)
|
||||
{
|
||||
match = null;
|
||||
if (string.IsNullOrEmpty(sha256) || string.IsNullOrEmpty(email))
|
||||
@@ -196,7 +224,7 @@ namespace BulkArchiveMailer.Core
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.Kind != kind)
|
||||
if (!string.Equals(entry.Kind, kind, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user