33e1802e6d
Code-review finding UI-Med-2: the design doc + delivery adapter treat FromNumber and MessagingServiceSid as either-or, but the entity ctor, EF schema, UI and CLI all hard- required FromNumber — so a Messaging-Service-only Twilio config (a normal production setup) could not be created. Bring the implementation into line with the spec: - Commons: SmsConfiguration.FromNumber -> string? (ctor fromNumber optional); UpdateSmsConfigCommand.FromNumber -> string?. - ConfigurationDatabase: FromNumber.IsRequired(false) + migration SmsFromNumberOptional (ALTER COLUMN nullable, idempotent; Down backfills '' — harmless, MsgSid keeps it deliverable) + regenerated model snapshot. - Transport: SmsConfigDto.FromNumber -> string? (round-trips a Messaging-Service-only config). - CentralUI: form validation requires AccountSid + at-least-one-of(FromNumber, MsgSid); nullable create/edit paths; From-number help text. - CLI: --from-number no longer Required; BuildUpdateSmsConfigCommand validates the either-or. - Adapter: From branch null-forgiving (guarded by the existing incomplete-config check). Tests: ManagementActor MsgSid-only persists null FromNumber; CLI MsgSid-only builds + neither-throws + contract (--from-number not Required); CentralUI MsgSid-only save.
131 lines
4.5 KiB
C#
131 lines
4.5 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Configurations;
|
|
|
|
public class NotificationListConfiguration : IEntityTypeConfiguration<NotificationList>
|
|
{
|
|
/// <summary>Configures the EF Core mapping for <see cref="NotificationList"/>.</summary>
|
|
/// <param name="builder">The entity type builder.</param>
|
|
public void Configure(EntityTypeBuilder<NotificationList> builder)
|
|
{
|
|
builder.HasKey(n => n.Id);
|
|
|
|
builder.Property(n => n.Name)
|
|
.IsRequired()
|
|
.HasMaxLength(200);
|
|
|
|
builder.Property(n => n.Type)
|
|
.HasConversion<string>()
|
|
.HasMaxLength(32)
|
|
.IsRequired();
|
|
|
|
builder.HasMany(n => n.Recipients)
|
|
.WithOne()
|
|
.HasForeignKey(r => r.NotificationListId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
builder.HasIndex(n => n.Name).IsUnique();
|
|
}
|
|
}
|
|
|
|
public class NotificationRecipientConfiguration : IEntityTypeConfiguration<NotificationRecipient>
|
|
{
|
|
/// <summary>Configures the EF Core mapping for <see cref="NotificationRecipient"/>.</summary>
|
|
/// <param name="builder">The entity type builder.</param>
|
|
public void Configure(EntityTypeBuilder<NotificationRecipient> builder)
|
|
{
|
|
builder.HasKey(r => r.Id);
|
|
|
|
builder.Property(r => r.Name)
|
|
.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(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>
|
|
{
|
|
/// <inheritdoc />
|
|
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);
|
|
|
|
// Optional: a Twilio Messaging-Service-only config has no From number (a valid
|
|
// config carries a FromNumber and/or a MessagingServiceSid).
|
|
builder.Property(s => s.FromNumber)
|
|
.IsRequired(false)
|
|
.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();
|
|
}
|
|
}
|
|
|
|
public class SmtpConfigurationConfiguration : IEntityTypeConfiguration<SmtpConfiguration>
|
|
{
|
|
/// <summary>Configures the EF Core mapping for <see cref="SmtpConfiguration"/>.</summary>
|
|
/// <param name="builder">The entity type builder.</param>
|
|
public void Configure(EntityTypeBuilder<SmtpConfiguration> builder)
|
|
{
|
|
builder.HasKey(s => s.Id);
|
|
|
|
builder.Property(s => s.Host)
|
|
.IsRequired()
|
|
.HasMaxLength(500);
|
|
|
|
builder.Property(s => s.AuthType)
|
|
.IsRequired()
|
|
.HasMaxLength(50);
|
|
|
|
// Stored encrypted at rest (EncryptedStringConverter). Ciphertext is larger than
|
|
// the plaintext, so the column is sized generously to avoid truncation.
|
|
builder.Property(s => s.Credentials)
|
|
.HasMaxLength(8000);
|
|
|
|
builder.Property(s => s.TlsMode)
|
|
.HasMaxLength(50);
|
|
|
|
builder.Property(s => s.FromAddress)
|
|
.IsRequired()
|
|
.HasMaxLength(500);
|
|
}
|
|
}
|