Files
ScadaBridge/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Migrations/20260619135543_AddSmsNotifications.cs
T
Joseph Doherty 9cff87fe85 docs(comments): strip internal task/milestone/bundle bookkeeping from code comments
Remove project bookkeeping citations from shipped code comments across the
solution: hyphenated task IDs (WP-14, StoreAndForward-025), milestone/task/
issue refs (M3, Task 4, Audit Log #23, #21), Bundle X task-bundle labels,
and C/D/K/S/T phase labels.

Comment text only — no code logic, string/log literals, or XML-doc structure
changed. Genuine descriptions are preserved (only the citation is stripped),
and technical lookalikes are retained (UTF-8, SHA-256, T00:00:00, M365,
UTC-5, pre-C4/pre-C5 schema versions). Flagged by the new CommentChecker
TaskReferenceInComment / TrackingReferenceInComment checks plus targeted
grep passes; full solution builds clean, append-only guard tests pass.
2026-07-07 11:03:26 -04:00

82 lines
4.4 KiB
C#

using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <summary>
/// SMS Notifications: makes a notification recipient's contact path optional and adds
/// the central SMS-provider (Twilio) configuration table.
///
/// 1. <c>NotificationRecipients.EmailAddress</c> becomes nullable — an SMS-only recipient
/// carries a <c>PhoneNumber</c> and no email. (ALTER COLUMN, naturally idempotent.)
/// 2. <c>NotificationRecipients.PhoneNumber nvarchar(32) NULL</c> — E.164 phone for SMS
/// recipients (guarded ADD).
/// 3. <c>SmsConfigurations</c> table — central-only provider config; <c>AuthToken</c> is
/// stored encrypted at rest (EncryptedStringConverter → <c>nvarchar(max)</c>, mirroring
/// <c>SmtpConfigurations.Credentials</c>) (guarded CREATE TABLE).
///
/// Every DDL statement is guarded so the migration is safe to re-run against a
/// partially-migrated database (the repo's idempotent-migration convention; see
/// AddListAttributeElementType).
/// </summary>
public partial class AddSmsNotifications : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// ALTER COLUMN is naturally idempotent: re-running re-applies the same nullable shape.
migrationBuilder.Sql("ALTER TABLE [NotificationRecipients] ALTER COLUMN [EmailAddress] nvarchar(500) NULL;");
migrationBuilder.Sql(@"
IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE Name='PhoneNumber' AND Object_ID=Object_ID('NotificationRecipients'))
ALTER TABLE [NotificationRecipients] ADD [PhoneNumber] nvarchar(32) NULL;");
migrationBuilder.Sql(@"
IF OBJECT_ID(N'dbo.SmsConfigurations', N'U') IS NULL
CREATE TABLE [SmsConfigurations] (
[Id] int NOT NULL IDENTITY(1, 1),
[AccountSid] nvarchar(100) NOT NULL,
[AuthToken] nvarchar(max) NULL,
[FromNumber] nvarchar(32) NOT NULL,
[MessagingServiceSid] nvarchar(100) NULL,
[ApiBaseUrl] nvarchar(500) NULL,
[ConnectionTimeoutSeconds] int NOT NULL,
[MaxRetries] int NOT NULL,
[RetryDelay] time NOT NULL,
CONSTRAINT [PK_SmsConfigurations] PRIMARY KEY ([Id])
);");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
// ConfigurationDatabase-NNN: refuse to roll back if SMS-only recipients exist.
// Such a row carries a PhoneNumber and a (legitimately) NULL EmailAddress. The
// backfill below sets every NULL EmailAddress to '' so the column can revert to
// NOT NULL — but for an SMS-only recipient that silently destroys its contact
// info (PhoneNumber is also dropped) and leaves an invalid empty-string email.
// This guard MUST run before DROP COLUMN [PhoneNumber], while the column is still
// queryable, so the operator gets a clear error instead of silent data loss.
migrationBuilder.Sql(@"
IF EXISTS (SELECT 1 FROM [NotificationRecipients] WHERE [EmailAddress] IS NULL AND [PhoneNumber] IS NOT NULL)
THROW 60000, 'Cannot roll back AddSmsNotifications: SMS-only recipients (PhoneNumber set, EmailAddress NULL) exist. Reassign or delete them before rolling back -- otherwise their PhoneNumber would be dropped and EmailAddress backfilled to an invalid empty string.', 1;");
migrationBuilder.Sql(@"
IF OBJECT_ID(N'dbo.SmsConfigurations', N'U') IS NOT NULL
DROP TABLE [SmsConfigurations];");
migrationBuilder.Sql(@"
IF EXISTS (SELECT 1 FROM sys.columns WHERE Name='PhoneNumber' AND Object_ID=Object_ID('NotificationRecipients'))
ALTER TABLE [NotificationRecipients] DROP COLUMN [PhoneNumber];");
// Reversing to NOT NULL requires backfilling existing NULLs first, otherwise the
// ALTER fails. Mirrors EF's generated default ('') for the previously-required column.
// The guard above guarantees any remaining NULL here belongs to a pre-existing email
// recipient that never had an address, not an SMS-only recipient.
migrationBuilder.Sql("UPDATE [NotificationRecipients] SET [EmailAddress] = '' WHERE [EmailAddress] IS NULL;");
migrationBuilder.Sql("ALTER TABLE [NotificationRecipients] ALTER COLUMN [EmailAddress] nvarchar(500) NOT NULL;");
}
}
}