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:
+51
-1
@@ -42,9 +42,59 @@ public class NotificationRecipientConfiguration : IEntityTypeConfiguration<Notif
|
||||
.IsRequired()
|
||||
.HasMaxLength(200);
|
||||
|
||||
// EmailAddress is now nullable — an SMS-only recipient carries a PhoneNumber and no
|
||||
// email. The max length is unchanged from the original email-only mapping.
|
||||
builder.Property(r => r.EmailAddress)
|
||||
.IsRequired()
|
||||
.IsRequired(false)
|
||||
.HasMaxLength(500);
|
||||
|
||||
// PhoneNumber (E.164, e.g. +14155552671) — nullable, present only for SMS recipients.
|
||||
builder.Property(r => r.PhoneNumber)
|
||||
.IsRequired(false)
|
||||
.HasMaxLength(32);
|
||||
}
|
||||
}
|
||||
|
||||
public class SmsConfigurationConfiguration : IEntityTypeConfiguration<SmsConfiguration>
|
||||
{
|
||||
/// <summary>Configures the EF Core mapping for <see cref="SmsConfiguration"/>.</summary>
|
||||
/// <param name="builder">The entity type builder.</param>
|
||||
public void Configure(EntityTypeBuilder<SmsConfiguration> builder)
|
||||
{
|
||||
builder.HasKey(s => s.Id);
|
||||
|
||||
builder.Property(s => s.AccountSid)
|
||||
.IsRequired()
|
||||
.HasMaxLength(100);
|
||||
|
||||
// Stored encrypted at rest (EncryptedStringConverter, wired in
|
||||
// ScadaBridgeDbContext.ApplySecretColumnEncryption). Ciphertext is larger than the
|
||||
// plaintext, so the column is sized generously to avoid truncation — mirrors
|
||||
// SmtpConfiguration.Credentials.
|
||||
builder.Property(s => s.AuthToken)
|
||||
.HasMaxLength(8000);
|
||||
|
||||
builder.Property(s => s.FromNumber)
|
||||
.IsRequired()
|
||||
.HasMaxLength(32);
|
||||
|
||||
builder.Property(s => s.MessagingServiceSid)
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder.Property(s => s.ApiBaseUrl)
|
||||
.HasMaxLength(500);
|
||||
|
||||
// The non-parameter constructor seeds ConnectionTimeoutSeconds/MaxRetries/RetryDelay
|
||||
// with sensible defaults; mapping them as REQUIRED columns preserves round-trip
|
||||
// fidelity for those values (S1 review note).
|
||||
builder.Property(s => s.ConnectionTimeoutSeconds)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(s => s.MaxRetries)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(s => s.RetryDelay)
|
||||
.IsRequired();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user