using Microsoft.EntityFrameworkCore.Migrations; #nullable disable namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations { /// /// SMS Notifications: makes a notification recipient's contact path optional and adds /// the central SMS-provider (Twilio) configuration table. /// /// 1. NotificationRecipients.EmailAddress becomes nullable — an SMS-only recipient /// carries a PhoneNumber and no email. (ALTER COLUMN, naturally idempotent.) /// 2. NotificationRecipients.PhoneNumber nvarchar(32) NULL — E.164 phone for SMS /// recipients (guarded ADD). /// 3. SmsConfigurations table — central-only provider config; AuthToken is /// stored encrypted at rest (EncryptedStringConverter → nvarchar(max), mirroring /// SmtpConfigurations.Credentials) (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). /// public partial class AddSmsNotifications : Migration { /// 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]) );"); } /// 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;"); } } }