diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Notifications/NotificationRecipient.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Notifications/NotificationRecipient.cs index 71ab9d89..055176e7 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Notifications/NotificationRecipient.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Notifications/NotificationRecipient.cs @@ -71,8 +71,9 @@ public class NotificationRecipient } /// - /// Parameterless constructor used by EF Core materialization and the SMS factory, where the - /// contact field is assigned via property setters rather than constructor parameters. + /// Private parameterless constructor that backs the factory path, + /// where the contact field is assigned via property setters rather than constructor + /// parameters — without exposing a half-initialized public constructor to callers. /// private NotificationRecipient() { diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Notifications/SmsConfiguration.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Notifications/SmsConfiguration.cs index 7db9f6ad..ececac0f 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Notifications/SmsConfiguration.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Notifications/SmsConfiguration.cs @@ -6,7 +6,10 @@ public class SmsConfiguration public int Id { get; set; } /// Gets or sets the Twilio Account SID. public string AccountSid { get; set; } - /// Gets or sets the Twilio Auth Token (secret), or null when not applicable. + /// + /// Gets or sets the Twilio Auth Token (secret). Stored encrypted; null only transiently + /// during configuration, never a valid production value. + /// public string? AuthToken { get; set; } /// Gets or sets the sender phone number (E.164) placed in the From field. public string FromNumber { get; set; } diff --git a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs index b00b5cfd..19d6828c 100644 --- a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs +++ b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs @@ -179,7 +179,7 @@ public class ArtifactDeploymentService // Map notification lists var notificationListArtifacts = notificationLists.Select(nl => - new NotificationListArtifact(nl.Name, nl.Recipients.Select(r => r.EmailAddress).ToList())).ToList(); + new NotificationListArtifact(nl.Name, nl.Recipients.Where(r => r.EmailAddress is not null).Select(r => r.EmailAddress!).ToList())).ToList(); // Map SMTP configurations — use Host as the artifact name (matches SQLite PK on site) var smtpArtifacts = smtpConfigurations.Select(smtp => diff --git a/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/Delivery/EmailNotificationDeliveryAdapter.cs b/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/Delivery/EmailNotificationDeliveryAdapter.cs index 97baed46..57d3ec4f 100644 --- a/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/Delivery/EmailNotificationDeliveryAdapter.cs +++ b/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/Delivery/EmailNotificationDeliveryAdapter.cs @@ -106,7 +106,10 @@ public sealed class EmailNotificationDeliveryAdapter : INotificationDeliveryAdap return DeliveryOutcome.Permanent(addressError); } - var recipientAddresses = recipients.Select(r => r.EmailAddress).ToList(); + var recipientAddresses = recipients + .Where(r => r.EmailAddress is not null) + .Select(r => r.EmailAddress!) + .ToList(); try { diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/SiteNotificationRepository.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/SiteNotificationRepository.cs index 5fea2b96..e3fb966a 100644 --- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/SiteNotificationRepository.cs +++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/SiteNotificationRepository.cs @@ -193,6 +193,24 @@ public class SiteNotificationRepository : INotificationRepository public Task DeleteSmtpConfigurationAsync(int id, CancellationToken cancellationToken = default) => throw new NotSupportedException("Managed via artifact deployment from Central"); + // ── SmsConfiguration (central-only — never deployed to or served from the site) ── + + /// + public Task GetSmsConfigurationAsync(CancellationToken cancellationToken = default) + => throw new NotSupportedException("Managed via artifact deployment from Central"); + + /// + public Task> GetAllSmsConfigurationsAsync(CancellationToken cancellationToken = default) + => throw new NotSupportedException("Managed via artifact deployment from Central"); + + /// + public Task AddSmsConfigurationAsync(SmsConfiguration smsConfiguration, CancellationToken cancellationToken = default) + => throw new NotSupportedException("Managed via artifact deployment from Central"); + + /// + public Task UpdateSmsConfigurationAsync(SmsConfiguration smsConfiguration, CancellationToken cancellationToken = default) + => throw new NotSupportedException("Managed via artifact deployment from Central"); + /// public Task SaveChangesAsync(CancellationToken cancellationToken = default) => throw new NotSupportedException("Managed via artifact deployment from Central"); diff --git a/src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/EntitySerializer.cs b/src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/EntitySerializer.cs index 579182e9..e5f7807b 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/EntitySerializer.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/EntitySerializer.cs @@ -138,9 +138,11 @@ public sealed class EntitySerializer NotificationLists: aggregate.NotificationLists.Select(nl => new NotificationListDto( Name: nl.Name, Type: nl.Type, - Recipients: nl.Recipients.Select(r => new NotificationRecipientDto( - Name: r.Name, - EmailAddress: r.EmailAddress)).ToList())).ToList(), + Recipients: nl.Recipients + .Where(r => r.EmailAddress is not null) + .Select(r => new NotificationRecipientDto( + Name: r.Name, + EmailAddress: r.EmailAddress!)).ToList())).ToList(), SmtpConfigs: aggregate.SmtpConfigurations.Select(smtp => { SecretsBlock? secrets = null;