diff --git a/src/Altinn.Notifications.Core/Enums/SmsNotificationResultType.cs b/src/Altinn.Notifications.Core/Enums/SmsNotificationResultType.cs new file mode 100644 index 00000000..03b2239a --- /dev/null +++ b/src/Altinn.Notifications.Core/Enums/SmsNotificationResultType.cs @@ -0,0 +1,32 @@ +namespace Altinn.Notifications.Core.Enums; + +/// +/// Enum describing sms notification result types +/// +public enum SmsNotificationResultType +{ + /// + /// Default result for new notifications + /// + New, + + /// + /// Sms notification being sent + /// + Sending, + + /// + /// Sms notification sent to service provider + /// + Accepted, + + /// + /// Failed, unknown reason + /// + Failed, + + /// + /// Failed, invalid mobilenumber + /// + Failed_InvalidRecipient +} diff --git a/src/Altinn.Notifications.Core/Models/Notification/SmsNotification.cs b/src/Altinn.Notifications.Core/Models/Notification/SmsNotification.cs new file mode 100644 index 00000000..54077d4d --- /dev/null +++ b/src/Altinn.Notifications.Core/Models/Notification/SmsNotification.cs @@ -0,0 +1,34 @@ +using Altinn.Notifications.Core.Enums; + +namespace Altinn.Notifications.Core.Models.Notification; + +/// +/// Class describing an sns notification and extends the +/// +public class SmsNotification : INotification +{ + /// + public Guid Id { get; internal set; } + + /// + public Guid OrderId { get; internal set; } + + /// + public DateTime RequestedSendTime { get; internal set; } + + /// + public NotificationChannel NotificationChannel { get; } = NotificationChannel.Sms; + + /// + /// Get the id of the recipient of the sms notification + /// + public string? RecipientId { get; internal set; } + + /// + /// Get or sets the mobilenumber of the sms notification + /// + public string RecipientNumber { get; internal set; } = string.Empty; + + /// + public NotificationResult SendResult { get; internal set; } = new(SmsNotificationResultType.New, DateTime.UtcNow); +} diff --git a/src/Altinn.Notifications.Core/Persistence/ISmsNotificationRepository.cs b/src/Altinn.Notifications.Core/Persistence/ISmsNotificationRepository.cs new file mode 100644 index 00000000..77968e81 --- /dev/null +++ b/src/Altinn.Notifications.Core/Persistence/ISmsNotificationRepository.cs @@ -0,0 +1,14 @@ +using Altinn.Notifications.Core.Models.Notification; + +namespace Altinn.Notifications.Core.Persistence; + +/// +/// Interface describing all repository operations related to an sms notification +/// +public interface ISmsNotificationRepository +{ + /// + /// Adds a new sms notification to the database + /// + public Task AddNotification(SmsNotification notification, DateTime expiry); +} diff --git a/src/Altinn.Notifications.Persistence/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.Notifications.Persistence/Extensions/ServiceCollectionExtensions.cs index 13683684..fdde86ea 100644 --- a/src/Altinn.Notifications.Persistence/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.Notifications.Persistence/Extensions/ServiceCollectionExtensions.cs @@ -31,6 +31,7 @@ public static IServiceCollection AddPostgresRepositories(this IServiceCollection return services .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddNpgsqlDataSource(connectionString, builder => diff --git a/src/Altinn.Notifications.Persistence/Migration/v0.17/01-setup-procedures.sql b/src/Altinn.Notifications.Persistence/Migration/v0.17/01-setup-procedures.sql new file mode 100644 index 00000000..b2937cce --- /dev/null +++ b/src/Altinn.Notifications.Persistence/Migration/v0.17/01-setup-procedures.sql @@ -0,0 +1,19 @@ +CREATE OR REPLACE PROCEDURE notifications.insertsmsnotification(_orderid uuid, + _alternateid uuid, + _recipientid TEXT, + _mobilenumber TEXT, + _result text, + _resulttime timestamptz, + _expirytime timestamptz + ) +LANGUAGE 'plpgsql' +AS $BODY$ +DECLARE +__orderid BIGINT := (SELECT _id from notifications.orders + where alternateid = _orderid); +BEGIN + +INSERT INTO notifications.smsnotifications(_orderid, alternateid, recipientid, mobilenumber, result, resulttime, expirytime) + VALUES (__orderid, _alternateid, _recipientid, _mobilenumber, _result::smsnotificationresulttype, _resulttime, _expirytime); +END; +$BODY$ \ No newline at end of file diff --git a/src/Altinn.Notifications.Persistence/Repository/SmsNotificationRepository.cs b/src/Altinn.Notifications.Persistence/Repository/SmsNotificationRepository.cs new file mode 100644 index 00000000..560e46f8 --- /dev/null +++ b/src/Altinn.Notifications.Persistence/Repository/SmsNotificationRepository.cs @@ -0,0 +1,47 @@ +using Altinn.Notifications.Core.Models.Notification; +using Altinn.Notifications.Core.Persistence; +using Microsoft.ApplicationInsights; +using Npgsql; +using NpgsqlTypes; + +namespace Altinn.Notifications.Persistence.Repository; + +/// +/// Implementation of sms repository logic +/// +public class SmsNotificationRepository : ISmsNotificationRepository +{ + private readonly NpgsqlDataSource _dataSource; + private readonly TelemetryClient? _telemetryClient; + + private const string _insertSmsNotificationSql = "call notifications.insertsmsnotification($1, $2, $3, $4, $5, $6, $7)"; // (__orderid, _alternateid, _recipientid, _mobilenumber, _result, _resulttime, _expirytime) + + /// + /// Initializes a new instance of the class. + /// + /// The npgsql data source. + /// Telemetry client + public SmsNotificationRepository(NpgsqlDataSource dataSource, TelemetryClient? telemetryClient = null) + { + _dataSource = dataSource; + _telemetryClient = telemetryClient; + } + + /// + public async Task AddNotification(SmsNotification notification, DateTime expiry) + { + await using NpgsqlCommand pgcom = _dataSource.CreateCommand(_insertSmsNotificationSql); + using TelemetryTracker tracker = new(_telemetryClient, pgcom); + + pgcom.Parameters.AddWithValue(NpgsqlDbType.Uuid, notification.OrderId); + pgcom.Parameters.AddWithValue(NpgsqlDbType.Uuid, notification.Id); + pgcom.Parameters.AddWithValue(NpgsqlDbType.Text, notification.RecipientId ?? (object)DBNull.Value); + pgcom.Parameters.AddWithValue(NpgsqlDbType.Text, notification.RecipientNumber); + pgcom.Parameters.AddWithValue(NpgsqlDbType.Text, notification.SendResult.Result.ToString()); + pgcom.Parameters.AddWithValue(NpgsqlDbType.TimestampTz, notification.SendResult.ResultTime); + pgcom.Parameters.AddWithValue(NpgsqlDbType.TimestampTz, expiry); + + await pgcom.ExecuteNonQueryAsync(); + tracker.Track(); + } +} diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications.Persistence/SmsRepositoryTests.cs b/test/Altinn.Notifications.IntegrationTests/Notifications.Persistence/SmsRepositoryTests.cs new file mode 100644 index 00000000..56c13297 --- /dev/null +++ b/test/Altinn.Notifications.IntegrationTests/Notifications.Persistence/SmsRepositoryTests.cs @@ -0,0 +1,64 @@ +using Altinn.Notifications.Core.Enums; +using Altinn.Notifications.Core.Models.Notification; +using Altinn.Notifications.Core.Models.Orders; +using Altinn.Notifications.Core.Persistence; +using Altinn.Notifications.IntegrationTests.Utils; +using Altinn.Notifications.Persistence.Repository; +using Xunit; + +namespace Altinn.Notifications.IntegrationTests.Notifications.Persistence; + +public class SmsRepositoryTests : IAsyncLifetime +{ + private List orderIdsToDelete; + + public SmsRepositoryTests() + { + orderIdsToDelete = []; + } + + public async Task InitializeAsync() + { + await Task.CompletedTask; + } + + public async Task DisposeAsync() + { + string deleteSql = $@"DELETE from notifications.orders o where o.alternateid in ('{string.Join("','", orderIdsToDelete)}')"; + await PostgreUtil.RunSql(deleteSql); + } + + [Fact] + public async Task Create_SmsNotification() + { + // Arrange + Guid orderId = await PostgreUtil.PopulateDBWithOrderAndReturnId(); + orderIdsToDelete.Add(orderId); + + // Arrange + SmsNotificationRepository repo = (SmsNotificationRepository)ServiceUtil + .GetServices(new List() { typeof(ISmsNotificationRepository) }) + .First(i => i.GetType() == typeof(SmsNotificationRepository)); + + Guid notificationId = Guid.NewGuid(); + SmsNotification smsNotification = new() + { + Id = notificationId, + OrderId = orderId, + RequestedSendTime = DateTime.UtcNow, + RecipientId = "12345678", + RecipientNumber = "999999999", + }; + + await repo.AddNotification(smsNotification, DateTime.UtcNow); + + // Assert + string sql = $@"SELECT count(1) + FROM notifications.smsnotifications o + WHERE o.alternateid = '{notificationId}'"; + + int actualCount = await PostgreUtil.RunSqlReturnOutput(sql); + + Assert.Equal(1, actualCount); + } +}