feat(notifications): central SMS config + nullable recipient contact (S2)

Implement the central ConfigurationDatabase side of SMS notifications:

- NotificationConfiguration: EmailAddress now nullable (SMS-only recipients
  carry a PhoneNumber, no email); add PhoneNumber nvarchar(32); add
  SmsConfigurationConfiguration (AuthToken sized as the encrypted column,
  mirroring SmtpConfiguration.Credentials; timeout/retry mapped REQUIRED for
  ctor-default round-trip fidelity).
- ScadaBridgeDbContext: add SmsConfigurations DbSet, encrypt AuthToken at rest
  via EncryptedStringConverter, and cover SmsConfiguration in the schema-only
  secret-write guard.
- NotificationRepository: implement the four INotificationRepository SMS-config
  methods (resolves the 4x CS0535), mirroring the SMTP methods' stage-only /
  separate-SaveChangesAsync discipline.
- Migration AddSmsNotifications: idempotent (guarded) ALTER EmailAddress nullable,
  ADD PhoneNumber, CREATE SmsConfigurations; Down reverses cleanly (backfills
  NULL emails before restoring NOT NULL).
This commit is contained in:
Joseph Doherty
2026-06-19 09:57:55 -04:00
parent 095361b73f
commit b46691747c
6 changed files with 2143 additions and 2 deletions
@@ -105,6 +105,8 @@ public class ScadaBridgeDbContext : DbContext, IDataProtectionKeyContext
public DbSet<NotificationRecipient> NotificationRecipients => Set<NotificationRecipient>();
/// <summary>Gets the set of SMTP configurations.</summary>
public DbSet<SmtpConfiguration> SmtpConfigurations => Set<SmtpConfiguration>();
/// <summary>Gets the set of SMS configurations.</summary>
public DbSet<SmsConfiguration> SmsConfigurations => Set<SmsConfiguration>();
/// <summary>Gets the set of notifications.</summary>
public DbSet<Notification> Notifications => Set<Notification>();
@@ -247,6 +249,7 @@ public class ScadaBridgeDbContext : DbContext, IDataProtectionKeyContext
string? secretProperty = entry.Entity switch
{
SmtpConfiguration => nameof(SmtpConfiguration.Credentials),
SmsConfiguration => nameof(SmsConfiguration.AuthToken),
ExternalSystemDefinition => nameof(ExternalSystemDefinition.AuthConfiguration),
DatabaseConnectionDefinition => nameof(DatabaseConnectionDefinition.ConnectionString),
_ => null
@@ -326,6 +329,10 @@ public class ScadaBridgeDbContext : DbContext, IDataProtectionKeyContext
.Property(s => s.Credentials)
.HasConversion(converter);
modelBuilder.Entity<SmsConfiguration>()
.Property(s => s.AuthToken)
.HasConversion(converter);
modelBuilder.Entity<ExternalSystemDefinition>()
.Property(e => e.AuthConfiguration)
.HasConversion(converter);