From 095361b73fbcb907a7392bf7bc4e02805ec0a907 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 19 Jun 2026 09:51:07 -0400 Subject: [PATCH] =?UTF-8?q?fix(sms):=20repair=20S1=20build=20breaks=20?= =?UTF-8?q?=E2=80=94=20null-filter=20EmailAddress=20projections=20+=20Site?= =?UTF-8?q?NotificationRepository=20SMS=20stubs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit S1 made NotificationRecipient.EmailAddress nullable + added SmsConfiguration and four INotificationRepository SMS methods, breaking compilation beyond the intentionally-deferred central NotificationRepository. Fix 1 (CS8620/CS8604 nullable EmailAddress projections, email-only paths): - NotificationOutbox EmailNotificationDeliveryAdapter: filter non-null emails - DeploymentManager ArtifactDeploymentService: filter non-null emails - Transport EntitySerializer: filter non-null emails into NotificationRecipientDto Fix 2 (CS0535): stub the four SMS-config methods on SiteRuntime SiteNotificationRepository (central-only — NotSupportedException, matching the existing 'Managed via artifact deployment from Central' write-path pattern). Doc nits: reword NotificationRecipient private ctor and SmsConfiguration.AuthToken comments. The central ConfigurationDatabase.NotificationRepository compile break is left as-is (S2 implements those four methods). --- .../Notifications/NotificationRecipient.cs | 5 +++-- .../Entities/Notifications/SmsConfiguration.cs | 5 ++++- .../ArtifactDeploymentService.cs | 2 +- .../EmailNotificationDeliveryAdapter.cs | 5 ++++- .../Repositories/SiteNotificationRepository.cs | 18 ++++++++++++++++++ .../Serialization/EntitySerializer.cs | 8 +++++--- 6 files changed, 35 insertions(+), 8 deletions(-) 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;