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
@@ -824,7 +824,6 @@ namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("EmailAddress")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
@@ -836,6 +835,10 @@ namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
b.Property<int>("NotificationListId")
.HasColumnType("int");
b.Property<string>("PhoneNumber")
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.HasKey("Id");
b.HasIndex("NotificationListId");
@@ -843,6 +846,50 @@ namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
b.ToTable("NotificationRecipients");
});
modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications.SmsConfiguration", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("AccountSid")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("ApiBaseUrl")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("AuthToken")
.HasMaxLength(8000)
.HasColumnType("nvarchar(max)");
b.Property<int>("ConnectionTimeoutSeconds")
.HasColumnType("int");
b.Property<string>("FromNumber")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.Property<int>("MaxRetries")
.HasColumnType("int");
b.Property<string>("MessagingServiceSid")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<TimeSpan>("RetryDelay")
.HasColumnType("time");
b.HasKey("Id");
b.ToTable("SmsConfigurations");
});
modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications.SmtpConfiguration", b =>
{
b.Property<int>("Id")