diff --git a/src/LocalTest.csproj b/src/LocalTest.csproj index 57ebb5b1..817096ea 100644 --- a/src/LocalTest.csproj +++ b/src/LocalTest.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -17,10 +17,10 @@ + - diff --git a/src/Notifications/API/Controllers/EmailNotificationOrdersController.cs b/src/Notifications/API/Controllers/EmailNotificationOrdersController.cs index 633828df..9ac5b062 100644 --- a/src/Notifications/API/Controllers/EmailNotificationOrdersController.cs +++ b/src/Notifications/API/Controllers/EmailNotificationOrdersController.cs @@ -21,7 +21,6 @@ using Swashbuckle.AspNetCore.Annotations; using Swashbuckle.AspNetCore.Filters; #endif - namespace Altinn.Notifications.Controllers; /// @@ -84,15 +83,10 @@ public async Task> Post(EmailNotificationOrderRequestEx } #endif + var orderRequest = emailNotificationOrderRequest.MapToOrderRequest(creator); - Result result = await _orderRequestService.RegisterNotificationOrder(orderRequest); + NotificationOrderRequestResponse result = await _orderRequestService.RegisterNotificationOrder(orderRequest); - return result.Match( - order => - { - string selfLink = order.GetSelfLink(); - return Accepted(selfLink, new OrderIdExt(order.Id)); - }, - error => StatusCode(error.ErrorCode, error.ErrorMessage)); + return Accepted(result.OrderId!.GetSelfLinkFromOrderId(), result.MapToExternal()); } -} \ No newline at end of file +} diff --git a/src/Notifications/API/Controllers/SmsNotificationOrdersController.cs b/src/Notifications/API/Controllers/SmsNotificationOrdersController.cs index 4bfb04fb..69da6e50 100644 --- a/src/Notifications/API/Controllers/SmsNotificationOrdersController.cs +++ b/src/Notifications/API/Controllers/SmsNotificationOrdersController.cs @@ -85,15 +85,9 @@ public async Task> Post(SmsNotificationOrderRequestExt } #endif - NotificationOrderRequest orderRequest = smsNotificationOrderRequest.MapToOrderRequest(creator); - Result result = await _orderRequestService.RegisterNotificationOrder(orderRequest); + var orderRequest = smsNotificationOrderRequest.MapToOrderRequest(creator); + NotificationOrderRequestResponse result = await _orderRequestService.RegisterNotificationOrder(orderRequest); - return result.Match( - registeredOrder => - { - string selfLink = registeredOrder!.GetSelfLink(); - return Accepted(selfLink, new OrderIdExt(registeredOrder!.Id)); - }, - error => StatusCode(error.ErrorCode, error.ErrorMessage)); + return Accepted(result.OrderId.GetSelfLinkFromOrderId(), result.MapToExternal()); } } diff --git a/src/Notifications/API/Extensions/HttpContextExtensions.cs b/src/Notifications/API/Extensions/HttpContextExtensions.cs new file mode 100644 index 00000000..4db71ded --- /dev/null +++ b/src/Notifications/API/Extensions/HttpContextExtensions.cs @@ -0,0 +1,15 @@ +namespace Altinn.Notifications.Extensions; + +/// +/// Extensions for HTTP Context +/// +public static class HttpContextExtensions +{ + /// + /// Get the org string from the context items or null if it is not defined + /// + public static string? GetOrg(this HttpContext context) + { + return context.Items["Org"] as string; + } +} diff --git a/src/Notifications/API/Extensions/ResourceLinkExtensions.cs b/src/Notifications/API/Extensions/ResourceLinkExtensions.cs index a8ce73fa..6bc81b90 100644 --- a/src/Notifications/API/Extensions/ResourceLinkExtensions.cs +++ b/src/Notifications/API/Extensions/ResourceLinkExtensions.cs @@ -1,5 +1,4 @@ -#nullable enable -using Altinn.Notifications.Core.Models.Orders; +using Altinn.Notifications.Core.Models.Orders; using Altinn.Notifications.Models; namespace Altinn.Notifications.Extensions; @@ -39,7 +38,6 @@ public static void SetResourceLinks(this NotificationOrderExt order) { Self = self, Status = self + "/status", - Notifications = self + "/notifications" }; } @@ -56,26 +54,41 @@ public static void NotificationSummaryResourceLinks(this NotificationOrderWithSt string baseUri = $"{_baseUri}/notifications/api/v1/orders/{order!.Id}/notifications/"; - if (order.NotificationsStatusSummary?.Email != null) + NotificationsStatusSummaryExt? summary = order.NotificationsStatusSummary; + + if (summary?.Email != null) { - order.NotificationsStatusSummary.Email.Links = new() + summary.Email.Links = new() { Self = baseUri + "email" }; } + + if (summary?.Sms != null) + { + summary.Sms.Links = new() + { + Self = baseUri + "sms" + }; + } } /// /// Gets the self link for the provided notification order /// /// Exception if class has not been initialized in Program.cs - public static string GetSelfLink(this NotificationOrder order) + public static string GetSelfLinkFromOrderId(this Guid? orderId) { if (_baseUri == null) { throw new InvalidOperationException("ResourceLinkExtensions has not been initialized with the base URI."); } - return _baseUri + "/notifications/api/v1/orders/" + order!.Id; + if (orderId == null) + { + return string.Empty; + } + + return _baseUri + "/notifications/api/v1/orders/" + orderId.ToString(); } } diff --git a/src/Notifications/API/Mappers/NotificationOrderRequestResponseMapper.cs b/src/Notifications/API/Mappers/NotificationOrderRequestResponseMapper.cs new file mode 100644 index 00000000..7feee5fe --- /dev/null +++ b/src/Notifications/API/Mappers/NotificationOrderRequestResponseMapper.cs @@ -0,0 +1,33 @@ +using Altinn.Notifications.Core.Models.Orders; +using Altinn.Notifications.Models; + +namespace Altinn.Notifications.Mappers; + +/// +/// Mapper class +/// +public static class NotificationOrderRequestResponseMapper +{ + /// + /// Maps a to a + /// + public static NotificationOrderRequestResponseExt MapToExternal(this NotificationOrderRequestResponse requestResponse) + { + NotificationOrderRequestResponseExt ext = new() + { + OrderId = requestResponse.OrderId + }; + + if (requestResponse.RecipientLookup != null) + { + ext.RecipientLookup = new RecipientLookupResultExt + { + Status = Enum.Parse(requestResponse.RecipientLookup.Status.ToString(), true), + IsReserved = requestResponse.RecipientLookup?.IsReserved, + MissingContact = requestResponse.RecipientLookup?.MissingContact, + }; + } + + return ext; + } +} diff --git a/src/Notifications/API/Mappers/OrderMapper.cs b/src/Notifications/API/Mappers/OrderMapper.cs index e9836a1a..8c9717b8 100644 --- a/src/Notifications/API/Mappers/OrderMapper.cs +++ b/src/Notifications/API/Mappers/OrderMapper.cs @@ -1,8 +1,6 @@ -#nullable enable -using Altinn.Notifications.Core.Enums; +using Altinn.Notifications.Core.Enums; using Altinn.Notifications.Core.Models; using Altinn.Notifications.Core.Models.Address; -using Altinn.Notifications.Core.Models.Notification; using Altinn.Notifications.Core.Models.NotificationTemplate; using Altinn.Notifications.Core.Models.Orders; using Altinn.Notifications.Extensions; @@ -20,20 +18,36 @@ public static class OrderMapper /// public static NotificationOrderRequest MapToOrderRequest(this EmailNotificationOrderRequestExt extRequest, string creator) { - var emailTemplate = new EmailTemplate(null, extRequest.Subject, extRequest.Body, (EmailContentType)extRequest.ContentType); + var emailTemplate = new EmailTemplate( + null, + extRequest.Subject, + extRequest.Body, + (EmailContentType?)extRequest.ContentType ?? EmailContentType.Plain); + + List recipients = + extRequest.Recipients + .Select(r => + { + List addresses = new(); - var recipients = new List(); + if (!string.IsNullOrEmpty(r.EmailAddress)) + { + addresses.Add(new EmailAddressPoint(r.EmailAddress)); + } - recipients.AddRange( - extRequest.Recipients.Select(r => new Recipient(new List() { new EmailAddressPoint(r.EmailAddress!) }))); + return new Recipient(addresses, r.OrganizationNumber, r.NationalIdentityNumber); + }) + .ToList(); return new NotificationOrderRequest( extRequest.SendersReference, creator, - new(){ emailTemplate }, + new List() { emailTemplate }, extRequest.RequestedSendTime.ToUniversalTime(), NotificationChannel.Email, - recipients); + recipients, + extRequest.IgnoreReservation, + extRequest.ResourceId); } /// @@ -43,18 +57,30 @@ public static NotificationOrderRequest MapToOrderRequest(this SmsNotificationOrd { INotificationTemplate smsTemplate = new SmsTemplate(extRequest.SenderNumber, extRequest.Body); - List recipients = new(); + List recipients = + extRequest.Recipients + .Select(r => + { + List addresses = new(); - recipients.AddRange( - extRequest.Recipients.Select(r => new Recipient(new List() { new SmsAddressPoint(r.MobileNumber!) }))); + if (!string.IsNullOrEmpty(r.MobileNumber)) + { + addresses.Add(new SmsAddressPoint(r.MobileNumber)); + } + + return new Recipient(addresses, r.OrganizationNumber, r.NationalIdentityNumber); + }) + .ToList(); return new NotificationOrderRequest( extRequest.SendersReference, creator, - new(){ smsTemplate }, + new List() { smsTemplate }, extRequest.RequestedSendTime.ToUniversalTime(), NotificationChannel.Sms, - recipients); + recipients, + extRequest.IgnoreReservation, + extRequest.ResourceId); } /// @@ -66,6 +92,7 @@ public static NotificationOrderExt MapToNotificationOrderExt(this NotificationOr orderExt.MapBaseNotificationOrder(order); orderExt.Recipients = order.Recipients.MapToRecipientExt(); + orderExt.IgnoreReservation = order.IgnoreReservation; foreach (var template in order.Templates) { @@ -174,16 +201,17 @@ internal static List MapToRecipientExt(this List recipi recipientExt.AddRange( recipients.Select(r => new RecipientExt { - OrganisationNumber = r.OrganisationNumber, - NationalIdentityNumber = r.NationalIdentityNumber, EmailAddress = GetEmailFromAddressList(r.AddressInfo), - MobileNumber = GetMobileNumberFromAddressList(r.AddressInfo) + MobileNumber = GetMobileNumberFromAddressList(r.AddressInfo), + NationalIdentityNumber = r.NationalIdentityNumber, + OrganizationNumber = r.OrganizationNumber, + IsReserved = r.IsReserved })); return recipientExt; } - private static IBaseNotificationOrderExt MapBaseNotificationOrder(this IBaseNotificationOrderExt orderExt, IBaseNotificationOrder order) + private static BaseNotificationOrderExt MapBaseNotificationOrder(this BaseNotificationOrderExt orderExt, IBaseNotificationOrder order) { orderExt.Id = order.Id.ToString(); orderExt.SendersReference = order.SendersReference; @@ -191,6 +219,8 @@ private static IBaseNotificationOrderExt MapBaseNotificationOrder(this IBaseNoti orderExt.Creator = order.Creator.ShortName; orderExt.NotificationChannel = (NotificationChannelExt)order.NotificationChannel; orderExt.RequestedSendTime = order.RequestedSendTime; + orderExt.IgnoreReservation = order.IgnoreReservation; + orderExt.ResourceId = order.ResourceId; return orderExt; } diff --git a/src/Notifications/API/Models/BaseNotificationOrderExt.cs b/src/Notifications/API/Models/BaseNotificationOrderExt.cs new file mode 100644 index 00000000..e72b6c03 --- /dev/null +++ b/src/Notifications/API/Models/BaseNotificationOrderExt.cs @@ -0,0 +1,61 @@ +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Models; + +/// +/// A class representing the base properties of a registered notification order. +/// +/// +/// External representaion to be used in the API. +/// +public class BaseNotificationOrderExt +{ + /// + /// Gets or sets the id of the notification order + /// + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// + /// Gets or sets the senders reference of the notification + /// + [JsonPropertyName("sendersReference")] + public string? SendersReference { get; set; } + + /// + /// Gets or sets the requested send time of the notification + /// + [JsonPropertyName("requestedSendTime")] + public DateTime RequestedSendTime { get; set; } + + /// + /// Gets or sets the short name of the creator of the notification order + /// + [JsonPropertyName("creator")] + public string Creator { get; set; } = string.Empty; + + /// + /// Gets or sets the date and time of when the notification order was created + /// + [JsonPropertyName("created")] + public DateTime Created { get; set; } + + /// + /// Gets or sets the preferred notification channel of the notification order + /// + [JsonPropertyName("notificationChannel")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public NotificationChannelExt NotificationChannel { get; set; } + + /// + /// Gets or sets whether notifications generated by this order should ignore KRR reservations + /// + [JsonPropertyName("ignoreReservation")] + public bool? IgnoreReservation { get; set; } + + /// + /// Gets or sets the id of the resource that the notification is related to + /// + [JsonPropertyName("resourceId")] + public string? ResourceId { get; set; } +} diff --git a/src/Notifications/API/Models/EmailContentTypeExt.cs b/src/Notifications/API/Models/EmailContentTypeExt.cs index 22921275..76cca39c 100644 --- a/src/Notifications/API/Models/EmailContentTypeExt.cs +++ b/src/Notifications/API/Models/EmailContentTypeExt.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Altinn.Notifications.Models; +namespace Altinn.Notifications.Models; /// /// Enum describing available content types for an email. diff --git a/src/Notifications/API/Models/EmailNotificationOrderRequestExt.cs b/src/Notifications/API/Models/EmailNotificationOrderRequestExt.cs index 20153a7e..61a87e53 100644 --- a/src/Notifications/API/Models/EmailNotificationOrderRequestExt.cs +++ b/src/Notifications/API/Models/EmailNotificationOrderRequestExt.cs @@ -1,4 +1,4 @@ -#nullable enable +using System.ComponentModel.DataAnnotations; using System.Text.Json; using System.Text.Json.Serialization; @@ -8,45 +8,29 @@ namespace Altinn.Notifications.Models; /// Class representing an email notiication order request /// /// -/// External representaion to be used in the API. +/// External representation to be used in the API /// -public class EmailNotificationOrderRequestExt +public class EmailNotificationOrderRequestExt : NotificationOrderRequestBaseExt { /// - /// Gets or sets the subject of the email + /// Gets or sets the subject of the email /// [JsonPropertyName("subject")] + [Required] public string Subject { get; set; } = string.Empty; /// /// Gets or sets the body of the email /// [JsonPropertyName("body")] + [Required] public string Body { get; set; } = string.Empty; /// /// Gets or sets the content type of the email /// [JsonPropertyName("contentType")] - public EmailContentTypeExt ContentType { get; set; } = EmailContentTypeExt.Plain; - - /// - /// Gets or sets the send time of the email. Defaults to UtcNow. - /// - [JsonPropertyName("requestedSendTime")] - public DateTime RequestedSendTime { get; set; } = DateTime.UtcNow; - - /// - /// Gets or sets the senders reference on the notification - /// - [JsonPropertyName("sendersReference")] - public string? SendersReference { get; set; } - - /// - /// Gets or sets the list of recipients - /// - [JsonPropertyName("recipients")] - public List Recipients { get; set; } = new List(); + public EmailContentTypeExt? ContentType { get; set; } /// /// Json serialized the diff --git a/src/Notifications/API/Models/EmailNotificationSummaryExt.cs b/src/Notifications/API/Models/EmailNotificationSummaryExt.cs index 99db078a..3131a17e 100644 --- a/src/Notifications/API/Models/EmailNotificationSummaryExt.cs +++ b/src/Notifications/API/Models/EmailNotificationSummaryExt.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Altinn.Notifications.Core.Models.Notification { diff --git a/src/Notifications/API/Models/EmailNotificationWithResultExt.cs b/src/Notifications/API/Models/EmailNotificationWithResultExt.cs index 2e664978..49922d59 100644 --- a/src/Notifications/API/Models/EmailNotificationWithResultExt.cs +++ b/src/Notifications/API/Models/EmailNotificationWithResultExt.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Altinn.Notifications.Models; diff --git a/src/Notifications/API/Models/EmailTemplateExt.cs b/src/Notifications/API/Models/EmailTemplateExt.cs index 8e2c40c4..ad5a2d88 100644 --- a/src/Notifications/API/Models/EmailTemplateExt.cs +++ b/src/Notifications/API/Models/EmailTemplateExt.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Altinn.Notifications.Models; diff --git a/src/Notifications/API/Models/NotificationChannelExt.cs b/src/Notifications/API/Models/NotificationChannelExt.cs index 11bea3a2..2e1c0dd2 100644 --- a/src/Notifications/API/Models/NotificationChannelExt.cs +++ b/src/Notifications/API/Models/NotificationChannelExt.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Altinn.Notifications.Models; +namespace Altinn.Notifications.Models; /// /// Enum describing available notification channels. @@ -9,5 +8,10 @@ public enum NotificationChannelExt /// /// The selected channel for the notification is email. /// - Email + Email, + + /// + /// The selected channel for the notification is sms. + /// + Sms } diff --git a/src/Notifications/API/Models/NotificationOrderExt.cs b/src/Notifications/API/Models/NotificationOrderExt.cs index bd7f6bf3..4a49196b 100644 --- a/src/Notifications/API/Models/NotificationOrderExt.cs +++ b/src/Notifications/API/Models/NotificationOrderExt.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Altinn.Notifications.Models; @@ -9,33 +8,8 @@ namespace Altinn.Notifications.Models; /// /// External representaion to be used in the API. /// -public class NotificationOrderExt : IBaseNotificationOrderExt +public class NotificationOrderExt : BaseNotificationOrderExt { - /// > - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; - - /// > - [JsonPropertyName("creator")] - public string Creator { get; set; } = string.Empty; - - /// > - [JsonPropertyName("sendersReference")] - public string? SendersReference { get; set; } - - /// > - [JsonPropertyName("requestedSendTime")] - public DateTime RequestedSendTime { get; set; } - - /// > - [JsonPropertyName("created")] - public DateTime Created { get; set; } - - /// > - [JsonPropertyName("notificationChannel")] - [JsonConverter(typeof(JsonStringEnumConverter))] - public NotificationChannelExt NotificationChannel { get; set; } - /// /// Gets or sets the list of recipients /// diff --git a/src/Notifications/API/Models/NotificationOrderListExt.cs b/src/Notifications/API/Models/NotificationOrderListExt.cs index e21362ae..ba315592 100644 --- a/src/Notifications/API/Models/NotificationOrderListExt.cs +++ b/src/Notifications/API/Models/NotificationOrderListExt.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Altinn.Notifications.Models; diff --git a/src/Notifications/API/Models/NotificationOrderRequestBaseExt.cs b/src/Notifications/API/Models/NotificationOrderRequestBaseExt.cs new file mode 100644 index 00000000..164626f6 --- /dev/null +++ b/src/Notifications/API/Models/NotificationOrderRequestBaseExt.cs @@ -0,0 +1,41 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Models; + +/// +/// Base class for common properties of notification order requests +/// +public class NotificationOrderRequestBaseExt +{ + /// + /// Gets or sets the send time of the email. Defaults to UtcNow + /// + [JsonPropertyName("requestedSendTime")] + public DateTime RequestedSendTime { get; set; } = DateTime.UtcNow; + + /// + /// Gets or sets the senders reference on the notification + /// + [JsonPropertyName("sendersReference")] + public string? SendersReference { get; set; } + + /// + /// Gets or sets the list of recipients + /// + [JsonPropertyName("recipients")] + [Required] + public List Recipients { get; set; } = new List(); + + /// + /// Gets or sets whether notifications generated by this order should ignore KRR reservations + /// + [JsonPropertyName("ignoreReservation")] + public bool? IgnoreReservation { get; set; } + + /// + /// Gets or sets the id of the resource that the notification is related to + /// + [JsonPropertyName("resourceId")] + public string? ResourceId { get; set; } +} diff --git a/src/Notifications/API/Models/NotificationOrderRequestResponseExt.cs b/src/Notifications/API/Models/NotificationOrderRequestResponseExt.cs new file mode 100644 index 00000000..ed701b5e --- /dev/null +++ b/src/Notifications/API/Models/NotificationOrderRequestResponseExt.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Models; + +/// +/// A class representing a container for an order id. +/// +/// +/// External representaion to be used in the API. +/// +public class NotificationOrderRequestResponseExt +{ + /// + /// The order id + /// + [JsonPropertyName("orderId")] + public Guid? OrderId { get; set; } + + /// + /// The recipient lookup summary + /// + [JsonPropertyName("recipientLookup")] + public RecipientLookupResultExt? RecipientLookup { get; set; } +} diff --git a/src/Notifications/API/Models/NotificationOrderWithStatusExt.cs b/src/Notifications/API/Models/NotificationOrderWithStatusExt.cs index 856bd3b2..46c22ced 100644 --- a/src/Notifications/API/Models/NotificationOrderWithStatusExt.cs +++ b/src/Notifications/API/Models/NotificationOrderWithStatusExt.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Altinn.Notifications.Models; @@ -9,33 +8,8 @@ namespace Altinn.Notifications.Models; /// /// External representation to be used in the API. /// -public class NotificationOrderWithStatusExt : IBaseNotificationOrderExt +public class NotificationOrderWithStatusExt : BaseNotificationOrderExt { - /// > - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; - - /// > - [JsonPropertyName("sendersReference")] - public string? SendersReference { get; set; } - - /// > - [JsonPropertyName("requestedSendTime")] - public DateTime RequestedSendTime { get; set; } - - /// > - [JsonPropertyName("creator")] - public string Creator { get; set; } = string.Empty; - - /// > - [JsonPropertyName("created")] - public DateTime Created { get; set; } - - /// > - [JsonPropertyName("notificationChannel")] - [JsonConverter(typeof(JsonStringEnumConverter))] - public NotificationChannelExt NotificationChannel { get; set; } - /// /// Gets or sets the processing status of the notication order /// diff --git a/src/Notifications/API/Models/NotificationResourceLinksExt.cs b/src/Notifications/API/Models/NotificationResourceLinksExt.cs index 72376c2d..e4cd97fc 100644 --- a/src/Notifications/API/Models/NotificationResourceLinksExt.cs +++ b/src/Notifications/API/Models/NotificationResourceLinksExt.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Altinn.Notifications.Models; diff --git a/src/Notifications/API/Models/NotificationStatusExt.cs b/src/Notifications/API/Models/NotificationStatusExt.cs index f8780a62..cacc865e 100644 --- a/src/Notifications/API/Models/NotificationStatusExt.cs +++ b/src/Notifications/API/Models/NotificationStatusExt.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Altinn.Notifications.Models; diff --git a/src/Notifications/API/Models/NotificationsStatusSummaryExt.cs b/src/Notifications/API/Models/NotificationsStatusSummaryExt.cs index 90b05f41..e3fb7c08 100644 --- a/src/Notifications/API/Models/NotificationsStatusSummaryExt.cs +++ b/src/Notifications/API/Models/NotificationsStatusSummaryExt.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Altinn.Notifications.Models; diff --git a/src/Notifications/API/Models/OrderResourceLinksExt.cs b/src/Notifications/API/Models/OrderResourceLinksExt.cs index 843f44ec..1fc50c07 100644 --- a/src/Notifications/API/Models/OrderResourceLinksExt.cs +++ b/src/Notifications/API/Models/OrderResourceLinksExt.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Altinn.Notifications.Models; @@ -22,10 +21,4 @@ public class OrderResourceLinksExt /// [JsonPropertyName("status")] public string Status { get; set; } = string.Empty; - - /// - /// Gets or sets the notifications link - /// - [JsonPropertyName("notifications")] - public string Notifications { get; set; } = string.Empty; } diff --git a/src/Notifications/API/Models/RecipientExt.cs b/src/Notifications/API/Models/RecipientExt.cs index fc38070a..91111f0d 100644 --- a/src/Notifications/API/Models/RecipientExt.cs +++ b/src/Notifications/API/Models/RecipientExt.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Altinn.Notifications.Models; @@ -24,14 +23,20 @@ public class RecipientExt public string? MobileNumber { get; set; } /// - /// Gets or sets the organisation number of the recipient + /// Gets or sets the organization number of the recipient /// - [JsonPropertyName("organisationNumber")] - public string? OrganisationNumber { get; set; } + [JsonPropertyName("organizationNumber")] + public string? OrganizationNumber { get; set; } /// /// Gets or sets the national identity number of the recipient /// [JsonPropertyName("nationalIdentityNumber")] public string? NationalIdentityNumber { get; set; } + + /// + /// Gets or sets a value indicating whether the recipient is reserved from digital communication + /// + [JsonPropertyName("isReserved")] + public bool? IsReserved { get; set; } } diff --git a/src/Notifications/API/Models/RecipientLookupResultExt.cs b/src/Notifications/API/Models/RecipientLookupResultExt.cs new file mode 100644 index 00000000..dfae24e3 --- /dev/null +++ b/src/Notifications/API/Models/RecipientLookupResultExt.cs @@ -0,0 +1,48 @@ +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Models; + +/// +/// Class describing a summary of recipient lookup for a notification order +/// +public class RecipientLookupResultExt +{ + /// + /// The lookup status + /// + [JsonPropertyName("status")] + public RecipientLookupStatusExt Status { get; set; } + + /// + /// List of id numbers for the recipients that are reserved + /// + [JsonPropertyName("isReserved")] + public List? IsReserved { get; set; } + + /// + /// List of id numbers for the recipients where no contact points were identified + /// + [JsonPropertyName("missingContact")] + public List? MissingContact { get; set; } +} + +/// +/// Enum describing the success rate for recipient lookup +/// +public enum RecipientLookupStatusExt +{ + /// + /// The recipient lookup was successful for all recipients + /// + Success, + + /// + /// The recipient lookup was successful for some recipients + /// + PartialSuccess, + + /// + /// The recipient lookup failed for all recipients + /// + Failed +} diff --git a/src/Notifications/API/Models/SmsNotificationOrderRequestExt.cs b/src/Notifications/API/Models/SmsNotificationOrderRequestExt.cs index 887f82d5..7747571c 100644 --- a/src/Notifications/API/Models/SmsNotificationOrderRequestExt.cs +++ b/src/Notifications/API/Models/SmsNotificationOrderRequestExt.cs @@ -1,4 +1,4 @@ -#nullable enable +using System.ComponentModel.DataAnnotations; using System.Text.Json; using System.Text.Json.Serialization; @@ -10,38 +10,21 @@ namespace Altinn.Notifications.Models; /// /// External representation to be used in the API. /// -public class SmsNotificationOrderRequestExt +public class SmsNotificationOrderRequestExt : NotificationOrderRequestBaseExt { /// /// Gets or sets the sender number of the SMS /// [JsonPropertyName("senderNumber")] - public string SenderNumber { get; set; } = string.Empty; + public string? SenderNumber { get; set; } /// /// Gets or sets the body of the SMS /// [JsonPropertyName("body")] + [Required] public string Body { get; set; } = string.Empty; - /// - /// Gets or sets the send time of the SMS. Defaults to UtcNow. - /// - [JsonPropertyName("requestedSendTime")] - public DateTime RequestedSendTime { get; set; } = DateTime.UtcNow; - - /// - /// Gets or sets the senders reference on the notification - /// - [JsonPropertyName("sendersReference")] - public string? SendersReference { get; set; } - - /// - /// Gets or sets the list of recipients - /// - [JsonPropertyName("recipients")] - public List Recipients { get; set; } = new List(); - /// /// Json serialized the /// diff --git a/src/Notifications/API/Models/SmsNotificationSummaryExt.cs b/src/Notifications/API/Models/SmsNotificationSummaryExt.cs new file mode 100644 index 00000000..96a340da --- /dev/null +++ b/src/Notifications/API/Models/SmsNotificationSummaryExt.cs @@ -0,0 +1,43 @@ +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Core.Models.Notification +{ + /// + /// A class representing an sms notification summary + /// + /// + /// External representaion to be used in the API. + /// + public class SmsNotificationSummaryExt + { + /// + /// The order id + /// + [JsonPropertyName("orderId")] + public Guid OrderId { get; set; } + + /// + /// The senders reference + /// + [JsonPropertyName("sendersReference")] + public string? SendersReference { get; set; } + + /// + /// The number of generated sms notifications + /// + [JsonPropertyName("generated")] + public int Generated { get; set; } + + /// + /// The number of sms notifications that were sent successfully + /// + [JsonPropertyName("succeeded")] + public int Succeeded { get; set; } + + /// + /// A list of notifications with send result + /// + [JsonPropertyName("notifications")] + public List Notifications { get; set; } = new(); + } +} diff --git a/src/Notifications/API/Models/SmsNotificationWithResultExt.cs b/src/Notifications/API/Models/SmsNotificationWithResultExt.cs new file mode 100644 index 00000000..822fc037 --- /dev/null +++ b/src/Notifications/API/Models/SmsNotificationWithResultExt.cs @@ -0,0 +1,39 @@ +using System.Text.Json.Serialization; + +using Altinn.Notifications.Models; + +namespace Altinn.Notifications.Core.Models.Notification +{ + /// + /// A class representing an sms notification with result + /// + /// + /// External representaion to be used in the API. + /// + public class SmsNotificationWithResultExt + { + /// + /// The notification id + /// + [JsonPropertyName("id")] + public Guid Id { get; set; } + + /// + /// Boolean indicating if the sending of the notification was successful + /// + [JsonPropertyName("succeeded")] + public bool Succeeded { get; set; } + + /// + /// The recipient of the notification + /// + [JsonPropertyName("recipient")] + public RecipientExt Recipient { get; set; } = new(); + + /// + /// The result status of the notification + /// + [JsonPropertyName("sendStatus")] + public StatusExt SendStatus { get; set; } = new(); + } +} diff --git a/src/Notifications/API/Models/StatusExt.cs b/src/Notifications/API/Models/StatusExt.cs index 4b7165ca..6a4ec24c 100644 --- a/src/Notifications/API/Models/StatusExt.cs +++ b/src/Notifications/API/Models/StatusExt.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Altinn.Notifications.Models; diff --git a/src/Notifications/API/Validators/EmailNotificationOrderRequestValidator.cs b/src/Notifications/API/Validators/EmailNotificationOrderRequestValidator.cs index d5165dce..8552995b 100644 --- a/src/Notifications/API/Validators/EmailNotificationOrderRequestValidator.cs +++ b/src/Notifications/API/Validators/EmailNotificationOrderRequestValidator.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using Altinn.Notifications.Models; @@ -20,12 +19,19 @@ public EmailNotificationOrderRequestValidator() RuleFor(order => order.Recipients) .NotEmpty() .WithMessage("One or more recipient is required.") - .Must(recipients => recipients.TrueForAll(a => IsValidEmail(a.EmailAddress))) - .WithMessage("A valid email address must be provided for all recipients."); + .Must(recipients => recipients.TrueForAll(a => + { + return + (!string.IsNullOrWhiteSpace(a.EmailAddress) && IsValidEmail(a.EmailAddress)) || + (!string.IsNullOrWhiteSpace(a.OrganizationNumber) ^ !string.IsNullOrWhiteSpace(a.NationalIdentityNumber)); + })) + .WithMessage("Either a valid email address, organization number, or national identity number must be provided for each recipient."); RuleFor(order => order.RequestedSendTime) - .Must(sendTime => sendTime >= DateTime.UtcNow.AddMinutes(-5)) - .WithMessage("Send time must be in the future. Leave blank to send immediately."); + .Must(sendTime => sendTime.Kind != DateTimeKind.Unspecified) + .WithMessage("The requested send time value must have specified a time zone.") + .Must(sendTime => sendTime >= DateTime.UtcNow.AddMinutes(-5)) + .WithMessage("Send time must be in the future. Leave blank to send immediately."); RuleFor(order => order.Body).NotEmpty(); RuleFor(order => order.Subject).NotEmpty(); diff --git a/src/Notifications/API/Validators/SmsNotificationOrderRequestValidator.cs b/src/Notifications/API/Validators/SmsNotificationOrderRequestValidator.cs index 7f073df7..425d7e97 100644 --- a/src/Notifications/API/Validators/SmsNotificationOrderRequestValidator.cs +++ b/src/Notifications/API/Validators/SmsNotificationOrderRequestValidator.cs @@ -1,9 +1,7 @@ -using System.Text.RegularExpressions; - +using Altinn.Notifications.Core.Helpers; using Altinn.Notifications.Models; using FluentValidation; -using PhoneNumbers; namespace Altinn.Notifications.Validators; @@ -20,8 +18,13 @@ public SmsNotificationOrderRequestValidator() RuleFor(order => order.Recipients) .NotEmpty() .WithMessage("One or more recipient is required.") - .Must(recipients => recipients.TrueForAll(a => IsValidMobileNumber(a.MobileNumber))) - .WithMessage("A valid mobile number starting with country code must be provided for all recipients."); + .Must(recipients => recipients.TrueForAll(a => + { + return + (!string.IsNullOrWhiteSpace(a.MobileNumber) && MobileNumberHelper.IsValidMobileNumber(a.MobileNumber)) || + (!string.IsNullOrWhiteSpace(a.OrganizationNumber) ^ !string.IsNullOrWhiteSpace(a.NationalIdentityNumber)); + })) + .WithMessage("Either a valid mobile number starting with country code, organization number, or national identity number must be provided for each recipient."); RuleFor(order => order.RequestedSendTime) .Must(sendTime => sendTime.Kind != DateTimeKind.Unspecified) @@ -31,26 +34,4 @@ public SmsNotificationOrderRequestValidator() RuleFor(order => order.Body).NotEmpty(); } - - /// - /// Validated as mobile number based on the Altinn 2 regex - /// - /// The string to validate as an mobile number - /// A boolean indicating that the mobile number is valid or not - internal static bool IsValidMobileNumber(string? mobileNumber) - { - if (string.IsNullOrEmpty(mobileNumber) || (!mobileNumber.StartsWith('+') && !mobileNumber.StartsWith("00"))) - { - return false; - } - - if (mobileNumber.StartsWith("00")) - { - mobileNumber = "+" + mobileNumber.Remove(0, 2); - } - - PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.GetInstance(); - PhoneNumber phoneNumber = phoneNumberUtil.Parse(mobileNumber, null); - return phoneNumberUtil.IsValidNumber(phoneNumber); - } } diff --git a/src/Notifications/API/Validators/ValidationResultExtensions.cs b/src/Notifications/API/Validators/ValidationResultExtensions.cs index 18ffe907..e5816351 100644 --- a/src/Notifications/API/Validators/ValidationResultExtensions.cs +++ b/src/Notifications/API/Validators/ValidationResultExtensions.cs @@ -1,5 +1,4 @@ -#nullable enable -using FluentValidation.Results; +using FluentValidation.Results; using Microsoft.AspNetCore.Mvc.ModelBinding; diff --git a/src/Notifications/Core/Configuration/NotificationOrderConfig.cs b/src/Notifications/Core/Configuration/NotificationOrderConfig.cs index dcb940c0..93bdeeae 100644 --- a/src/Notifications/Core/Configuration/NotificationOrderConfig.cs +++ b/src/Notifications/Core/Configuration/NotificationOrderConfig.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Altinn.Notifications.Core.Configuration; +namespace Altinn.Notifications.Core.Configuration; /// /// Configuration class for notification orders diff --git a/src/Notifications/Core/Enums/AddressType.cs b/src/Notifications/Core/Enums/AddressType.cs index 6c36e21e..46d76cb3 100644 --- a/src/Notifications/Core/Enums/AddressType.cs +++ b/src/Notifications/Core/Enums/AddressType.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Altinn.Notifications.Core.Enums; +namespace Altinn.Notifications.Core.Enums; #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member /// diff --git a/src/Notifications/Core/Enums/AltinnServiceUpdateSchema.cs b/src/Notifications/Core/Enums/AltinnServiceUpdateSchema.cs new file mode 100644 index 00000000..eee6bb13 --- /dev/null +++ b/src/Notifications/Core/Enums/AltinnServiceUpdateSchema.cs @@ -0,0 +1,18 @@ +namespace Altinn.Notifications.Core.Enums; + +/// +/// Enum describing the various Altinn Service update schemas +/// +public enum AltinnServiceUpdateSchema +{ + /// + /// Default value for unknown schema + /// + Unkown, + + /// + /// The resource limit exceeded schema + /// + /// Data of this schema should be mapped to a object + ResourceLimitExceeded +} diff --git a/src/Notifications/Core/Enums/EmailContentType.cs b/src/Notifications/Core/Enums/EmailContentType.cs index 991ae849..ad3b5c53 100644 --- a/src/Notifications/Core/Enums/EmailContentType.cs +++ b/src/Notifications/Core/Enums/EmailContentType.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Altinn.Notifications.Core.Enums; +namespace Altinn.Notifications.Core.Enums; /// /// Enum describing available email content types diff --git a/src/Notifications/Core/Enums/EmailNotificationResultType.cs b/src/Notifications/Core/Enums/EmailNotificationResultType.cs index 28e0562d..8761074f 100644 --- a/src/Notifications/Core/Enums/EmailNotificationResultType.cs +++ b/src/Notifications/Core/Enums/EmailNotificationResultType.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Altinn.Notifications.Core.Enums; +namespace Altinn.Notifications.Core.Enums; /// /// Enum describing email notification result types @@ -31,6 +30,11 @@ public enum EmailNotificationResultType /// Failed, + /// + /// Failed, recipient is reserved in KRR + /// + Failed_RecipientReserved, + /// /// Recipient to address was not identified /// @@ -39,5 +43,33 @@ public enum EmailNotificationResultType /// /// Invalid format for email address /// - Failed_InvalidEmailFormat + Failed_InvalidEmailFormat, + + /// + /// Recipient supressed by email service + /// + Failed_SupressedRecipient, + + /// + /// Transient error, retry later + /// + /// + /// Should not be used externally or persisted in db. + /// Only used for processing and logic in service layer. + Failed_TransientError, + + /// + /// Failed, bounced + /// + Failed_Bounced, + + /// + /// Failed, filtered spam + /// + Failed_FilteredSpam, + + /// + /// Failed, quarantined + /// + Failed_Quarantined } diff --git a/src/Notifications/Core/Enums/IResultType.cs b/src/Notifications/Core/Enums/IResultType.cs index d8dea4d1..b74d0b50 100644 --- a/src/Notifications/Core/Enums/IResultType.cs +++ b/src/Notifications/Core/Enums/IResultType.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Altinn.Notifications.Core.Enums; +namespace Altinn.Notifications.Core.Enums; /// /// Base class for send result of a notification diff --git a/src/Notifications/Core/Enums/NotificationChannel.cs b/src/Notifications/Core/Enums/NotificationChannel.cs index 62a73e1d..49e30aa8 100644 --- a/src/Notifications/Core/Enums/NotificationChannel.cs +++ b/src/Notifications/Core/Enums/NotificationChannel.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Altinn.Notifications.Core.Enums; +namespace Altinn.Notifications.Core.Enums; /// /// Enum describing available notification channels. diff --git a/src/Notifications/Core/Enums/NotificationTemplateType.cs b/src/Notifications/Core/Enums/NotificationTemplateType.cs index 24d208e0..de2da775 100644 --- a/src/Notifications/Core/Enums/NotificationTemplateType.cs +++ b/src/Notifications/Core/Enums/NotificationTemplateType.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Altinn.Notifications.Core.Enums; +namespace Altinn.Notifications.Core.Enums; #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member /// diff --git a/src/Notifications/Core/Enums/OrderProcessingStatus.cs b/src/Notifications/Core/Enums/OrderProcessingStatus.cs index 20c22fa9..757bb44f 100644 --- a/src/Notifications/Core/Enums/OrderProcessingStatus.cs +++ b/src/Notifications/Core/Enums/OrderProcessingStatus.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Altinn.Notifications.Core.Enums; +namespace Altinn.Notifications.Core.Enums; #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member /// diff --git a/src/Notifications/Core/Enums/SmsNotificationResultType.cs b/src/Notifications/Core/Enums/SmsNotificationResultType.cs new file mode 100644 index 00000000..77fdf705 --- /dev/null +++ b/src/Notifications/Core/Enums/SmsNotificationResultType.cs @@ -0,0 +1,72 @@ +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, + + /// + /// Sms notification was successfully delivered to destination. + /// + Delivered, + + /// + /// Sms notification send operation failed + /// + Failed, + + /// + /// Sms notification send operation failed due to invalid recipient + /// + Failed_InvalidRecipient, + + /// + /// Failed, recipient is reserved in KRR + /// + Failed_RecipientReserved, + + /// + /// Sms notification send operation failed because the receiver number is barred/blocked/not in use. + /// + Failed_BarredReceiver, + + /// + /// Sms notification send operation failed because the message has been deleted. + /// + Failed_Deleted, + + /// + /// Sms notification send operation failed because the message validity period has expired. + /// + Failed_Expired, + + /// + /// Sms notification send operation failed due to the SMS being undeliverable. + /// + Failed_Undelivered, + + /// + /// Recipient mobile number was not identified + /// + Failed_RecipientNotIdentified, + + /// + /// Message was rejected. + /// + Failed_Rejected +} diff --git a/src/Notifications/Core/Helpers/MobileNumberHelper.cs b/src/Notifications/Core/Helpers/MobileNumberHelper.cs new file mode 100644 index 00000000..f2bd74ca --- /dev/null +++ b/src/Notifications/Core/Helpers/MobileNumberHelper.cs @@ -0,0 +1,56 @@ +using PhoneNumbers; + +namespace Altinn.Notifications.Core.Helpers +{ + /// + /// Helper class for all mobile number related actions + /// + public static class MobileNumberHelper + { + /// + /// Checks if number contains country code, if not it adds the country code for Norway if number starts with 4 or 9 + /// + /// + /// This method does not validate the number, only ensures that it has a country code. + /// + public static string EnsureCountryCodeIfValidNumber(string mobileNumber) + { + if (string.IsNullOrEmpty(mobileNumber)) + { + return mobileNumber; + } + else if (mobileNumber.StartsWith("00")) + { + mobileNumber = "+" + mobileNumber.Remove(0, 2); + } + else if (mobileNumber.Length == 8 && (mobileNumber[0] == '9' || mobileNumber[0] == '4')) + { + mobileNumber = "+47" + mobileNumber; + } + + return mobileNumber; + } + + /// + /// Validated as mobile number based on the Altinn 2 regex + /// + /// The string to validate as an mobile number + /// A boolean indicating that the mobile number is valid or not + public static bool IsValidMobileNumber(string? mobileNumber) + { + if (string.IsNullOrEmpty(mobileNumber) || (!mobileNumber.StartsWith('+') && !mobileNumber.StartsWith("00"))) + { + return false; + } + + if (mobileNumber.StartsWith("00")) + { + mobileNumber = "+" + mobileNumber.Remove(0, 2); + } + + PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.GetInstance(); + PhoneNumber phoneNumber = phoneNumberUtil.Parse(mobileNumber, null); + return phoneNumberUtil.IsValidNumber(phoneNumber); + } + } +} diff --git a/src/Notifications/Core/Integrations/IAuthorizationService.cs b/src/Notifications/Core/Integrations/IAuthorizationService.cs new file mode 100644 index 00000000..a299b3cf --- /dev/null +++ b/src/Notifications/Core/Integrations/IAuthorizationService.cs @@ -0,0 +1,22 @@ +using Altinn.Notifications.Core.Models.ContactPoints; + +namespace Altinn.Notifications.Core.Integrations; + +/// +/// Describes the necessary functions of an authorization service that can perform +/// notification recipient filtering based on authorization +/// +public interface IAuthorizationService +{ + /// + /// Describes a method that can create an authorization request to authorize a set of + /// users for access to a resource. + /// + /// + /// The contact points of an organization including user registered contact points. + /// + /// The id of the resource. + /// A new list of with filtered list of recipients. + Task> AuthorizeUserContactPointsForResource( + List organizationContactPoints, string resourceId); +} diff --git a/src/Notifications/Core/Integrations/IProfileClient.cs b/src/Notifications/Core/Integrations/IProfileClient.cs new file mode 100644 index 00000000..0f786e9d --- /dev/null +++ b/src/Notifications/Core/Integrations/IProfileClient.cs @@ -0,0 +1,24 @@ +using Altinn.Notifications.Core.Models.ContactPoints; + +namespace Altinn.Notifications.Core.Integrations; + +/// +/// Interface describing a client for the profile service +/// +public interface IProfileClient +{ + /// + /// Retrieves contact points for a list of users corresponding to a list of national identity numbers + /// + /// A list of national identity numbers to look up contact points for + /// A list of contact points for the provided national identity numbers + public Task> GetUserContactPoints(List nationalIdentityNumbers); + + /// + /// Retrieves the user registered contact points for a list of organizations identified by organization numbers + /// + /// The set or organizations to retrieve contact points for + /// The id of the resource to look up contact points for + /// A list of organiation contact points containing user registered contact points + public Task> GetUserRegisteredContactPoints(List organizationNumbers, string resourceId); +} diff --git a/src/Notifications/Core/Integrations/IRegisterClient.cs b/src/Notifications/Core/Integrations/IRegisterClient.cs new file mode 100644 index 00000000..44135b33 --- /dev/null +++ b/src/Notifications/Core/Integrations/IRegisterClient.cs @@ -0,0 +1,17 @@ +using Altinn.Notifications.Core.Models.ContactPoints; + +namespace Altinn.Notifications.Core.Integrations +{ + /// + /// Interface describing a client for the register service + /// + public interface IRegisterClient + { + /// + /// Retrieves contact points for a list of organizations + /// + /// A list of organization numbers to look up contact points for + /// A list of for the provided organizations + public Task> GetOrganizationContactPoints(List organizationNumbers); + } +} diff --git a/src/Notifications/Core/JsonSerializerOptionsProvider.cs b/src/Notifications/Core/JsonSerializerOptionsProvider.cs new file mode 100644 index 00000000..890b9d4b --- /dev/null +++ b/src/Notifications/Core/JsonSerializerOptionsProvider.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Core +{ + /// + /// Provider class for JsonSerializerOptions + /// + public static class JsonSerializerOptionsProvider + { + /// + /// Standard serializer options + /// + public static JsonSerializerOptions Options { get; } = new() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = { new JsonStringEnumConverter() }, + PropertyNameCaseInsensitive = true + }; + } +} diff --git a/src/Notifications/Core/Models/Address/EmailAddressPoint.cs b/src/Notifications/Core/Models/Address/EmailAddressPoint.cs index aa2b3031..61a6b6f6 100644 --- a/src/Notifications/Core/Models/Address/EmailAddressPoint.cs +++ b/src/Notifications/Core/Models/Address/EmailAddressPoint.cs @@ -1,10 +1,9 @@ -#nullable enable -using Altinn.Notifications.Core.Enums; +using Altinn.Notifications.Core.Enums; namespace Altinn.Notifications.Core.Models.Address; /// -/// A class represeting an address point +/// A class represeting an email address point /// public class EmailAddressPoint : IAddressPoint { diff --git a/src/Notifications/Core/Models/Address/IAddressPoint.cs b/src/Notifications/Core/Models/Address/IAddressPoint.cs index af920db4..1c0ac961 100644 --- a/src/Notifications/Core/Models/Address/IAddressPoint.cs +++ b/src/Notifications/Core/Models/Address/IAddressPoint.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Altinn.Notifications.Core.Enums; diff --git a/src/Notifications/Core/Models/Address/SmsAddressPoint.cs b/src/Notifications/Core/Models/Address/SmsAddressPoint.cs index a09275aa..246943d4 100644 --- a/src/Notifications/Core/Models/Address/SmsAddressPoint.cs +++ b/src/Notifications/Core/Models/Address/SmsAddressPoint.cs @@ -1,5 +1,4 @@ -#nullable enable -using Altinn.Notifications.Core.Enums; +using Altinn.Notifications.Core.Enums; namespace Altinn.Notifications.Core.Models.Address; diff --git a/src/Notifications/Core/Models/AltinnServiceUpdate/GenericServiceUpdate.cs b/src/Notifications/Core/Models/AltinnServiceUpdate/GenericServiceUpdate.cs new file mode 100644 index 00000000..dad21498 --- /dev/null +++ b/src/Notifications/Core/Models/AltinnServiceUpdate/GenericServiceUpdate.cs @@ -0,0 +1,74 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +using Altinn.Notifications.Core.Enums; + +namespace Altinn.Notifications.Core.Models.AltinnServiceUpdate +{ + /// + /// A class representing a generic service update + /// + public class GenericServiceUpdate + { + private static readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, + WriteIndented = true, + Converters = { new JsonStringEnumConverter() }, + PropertyNameCaseInsensitive = true + }; + + /// + /// The source of the service update + /// + public string Source { get; set; } = string.Empty; + + /// + /// The schema of the service update data + /// + public AltinnServiceUpdateSchema Schema { get; set; } + + /// + /// The data of the service update as a json serialized string + /// + public string Data { get; set; } = string.Empty; + + /// + /// Serialize the into a json string + /// + /// + public string Serialize() + { + return JsonSerializer.Serialize(this, _serializerOptions); + } + + /// + /// Try to parse a json string into a + /// + public static bool TryParse(string input, out GenericServiceUpdate value) + { + GenericServiceUpdate? parsedOutput; + value = new GenericServiceUpdate(); + + if (string.IsNullOrEmpty(input)) + { + return false; + } + + try + { + parsedOutput = JsonSerializer.Deserialize(input!, _serializerOptions); + + value = parsedOutput!; + return !string.IsNullOrEmpty(value.Source); + } + catch + { + // try parse, we simply return false if fails + } + + return false; + } + } +} diff --git a/src/Notifications/Core/Models/AltinnServiceUpdate/ResourceLimitExceeded.cs b/src/Notifications/Core/Models/AltinnServiceUpdate/ResourceLimitExceeded.cs new file mode 100644 index 00000000..bbe2a654 --- /dev/null +++ b/src/Notifications/Core/Models/AltinnServiceUpdate/ResourceLimitExceeded.cs @@ -0,0 +1,67 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Core.Models.AltinnServiceUpdate +{ + /// + /// A class holding data on an exceeded resource limit in an Altinn service + /// + public class ResourceLimitExceeded + { + private static readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, + WriteIndented = true, + Converters = { new JsonStringEnumConverter() }, + PropertyNameCaseInsensitive = true + }; + + /// + /// The resource that has reached its capacity limit + /// + public string Resource { get; set; } = string.Empty; + + /// + /// The timestamp for when the service is available again + /// + public DateTime ResetTime { get; set; } + + /// + /// Serialize the into a json string + /// + /// + public string Serialize() + { + return JsonSerializer.Serialize(this, _serializerOptions); + } + + /// + /// Try to parse a json string into a + /// + public static bool Tryparse(string input, out ResourceLimitExceeded value) + { + ResourceLimitExceeded? parsedOutput; + value = new ResourceLimitExceeded(); + + if (string.IsNullOrEmpty(input)) + { + return false; + } + + try + { + parsedOutput = JsonSerializer.Deserialize(input!, _serializerOptions); + + value = parsedOutput!; + return !string.IsNullOrEmpty(value.Resource); + } + catch + { + // try parse, we simply return false if fails + } + + return false; + } + } +} diff --git a/src/Notifications/Core/Models/ContactPoints/OrganizationContactPoints.cs b/src/Notifications/Core/Models/ContactPoints/OrganizationContactPoints.cs new file mode 100644 index 00000000..269cf80a --- /dev/null +++ b/src/Notifications/Core/Models/ContactPoints/OrganizationContactPoints.cs @@ -0,0 +1,52 @@ +using System.Linq; + +namespace Altinn.Notifications.Core.Models.ContactPoints; + +/// +/// Class describing the contact points for an organization +/// +public class OrganizationContactPoints +{ + /// + /// Gets or sets the organization number for the organization + /// + public string OrganizationNumber { get; set; } = string.Empty; + + /// + /// Gets or sets the party id of the organization + /// + public int PartyId { get; set; } + + /// + /// Gets or sets a list of official mobile numbers + /// + public List MobileNumberList { get; set; } = new(); + + /// + /// Gets or sets a list of official email addresses + /// + public List EmailList { get; set; } = new(); + + /// + /// Gets or sets a list of user registered contact points associated with the organization. + /// + public List UserContactPoints { get; set; } = new(); + + /// + /// Create a new instance with the same values as the existing instance + /// + /// The new instance with copied values. + public OrganizationContactPoints CloneWithoutContactPoints() + { + OrganizationContactPoints clone = new() + { + OrganizationNumber = OrganizationNumber, + PartyId = PartyId, + MobileNumberList = new(), + EmailList = new(), + UserContactPoints = new() + }; + + return clone; + } +} diff --git a/src/Notifications/Core/Models/ContactPoints/UserContactPoints.cs b/src/Notifications/Core/Models/ContactPoints/UserContactPoints.cs new file mode 100644 index 00000000..38b058da --- /dev/null +++ b/src/Notifications/Core/Models/ContactPoints/UserContactPoints.cs @@ -0,0 +1,48 @@ +namespace Altinn.Notifications.Core.Models.ContactPoints; + +/// +/// Class describing the availability of contact points for a user +/// +public class UserContactPoints +{ + /// + /// Gets or sets the ID of the user + /// + public int UserId { get; set; } + + /// + /// Gets or sets the national identityt number of the user + /// + public string NationalIdentityNumber { get; set; } = string.Empty; + + /// + /// Gets or sets a boolean indicating whether the user has reserved themselves from electronic communication + /// + public bool IsReserved { get; set; } + + /// + /// Gets or sets the mobile number + /// + public string MobileNumber { get; set; } = string.Empty; + + /// + /// Gets or sets the email address + /// + public string Email { get; set; } = string.Empty; + + /// + /// Create a new instance with the same values as the existing instance + /// + /// The new instance with copied values. + public UserContactPoints Clone() + { + return new() + { + UserId = UserId, + NationalIdentityNumber = NationalIdentityNumber, + IsReserved = IsReserved, + MobileNumber = MobileNumber, + Email = Email + }; + } +} diff --git a/src/Notifications/Core/Models/Creator.cs b/src/Notifications/Core/Models/Creator.cs index ef0e334a..794e1d0c 100644 --- a/src/Notifications/Core/Models/Creator.cs +++ b/src/Notifications/Core/Models/Creator.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Altinn.Notifications.Core.Models; +namespace Altinn.Notifications.Core.Models; /// /// A class representing a notification creator diff --git a/src/Notifications/Core/Models/Email.cs b/src/Notifications/Core/Models/Email.cs index 00a3ecce..dcab68d8 100644 --- a/src/Notifications/Core/Models/Email.cs +++ b/src/Notifications/Core/Models/Email.cs @@ -1,6 +1,4 @@ -#nullable enable -using System.Text.Json; -using System.Text.Json.Serialization; +using System.Text.Json; using Altinn.Notifications.Core.Enums; @@ -59,13 +57,6 @@ public Email(Guid notificationId, string subject, string body, string fromAddres /// public string Serialize() { - return JsonSerializer.Serialize( - this, - new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = { new JsonStringEnumConverter() } - }); + return JsonSerializer.Serialize(this, JsonSerializerOptionsProvider.Options); } } diff --git a/src/Notifications/Core/Models/Notification/EmailNotification.cs b/src/Notifications/Core/Models/Notification/EmailNotification.cs deleted file mode 100644 index 3348ab48..00000000 --- a/src/Notifications/Core/Models/Notification/EmailNotification.cs +++ /dev/null @@ -1,57 +0,0 @@ -#nullable enable -using Altinn.Notifications.Core.Enums; - -namespace Altinn.Notifications.Core.Models.Notification; - -/// -/// Class describing an email notification and extends the -/// -public class EmailNotification : INotification -{ - /// - public Guid Id { get; internal set; } - - /// - public Guid OrderId { get; internal set; } - - /// - public DateTime RequestedSendTime { get; internal set; } - - /// - public NotificationChannel NotificationChannel { get; } = NotificationChannel.Email; - - /// - /// Get the id of the recipient of the email notification - /// - public string? RecipientId { get; internal set; } - - /// - /// Get or sets the to address of the email notification - /// - public string ToAddress { get; internal set; } = string.Empty; - - /// - /// Get or sets the send result of the notification - /// - public NotificationResult SendResult { get; internal set; } = new(EmailNotificationResultType.New, DateTime.UtcNow); - - /// - /// Initializes a new instance of the class. - /// - public EmailNotification(Guid orderId, DateTime sendTime) - { - Id = Guid.NewGuid(); - OrderId = orderId; - RequestedSendTime = sendTime; - } - - /// - /// Initializes a new instance of the class. - /// - internal EmailNotification() - { - Id = Guid.Empty; - OrderId = Guid.Empty; - RequestedSendTime = DateTime.MinValue; - } -} diff --git a/src/Notifications/Core/Models/Notification/EmailNotificationSummary.cs b/src/Notifications/Core/Models/Notification/EmailNotificationSummary.cs deleted file mode 100644 index 28d97540..00000000 --- a/src/Notifications/Core/Models/Notification/EmailNotificationSummary.cs +++ /dev/null @@ -1,32 +0,0 @@ -#nullable enable -namespace Altinn.Notifications.Core.Models.Notification -{ - /// - /// An implementation of for email notifications"/> - /// - public class EmailNotificationSummary : INotificationSummary - { - /// - public Guid OrderId { get; set; } - - /// - public string? SendersReference { get; set; } - - /// - public int Generated { get; internal set; } - - /// - public int Succeeded { get; internal set; } - - /// - public List Notifications { get; set; } = new List(); - - /// - /// Initializes a new instance of the class. - /// - public EmailNotificationSummary(Guid orderId) - { - OrderId = orderId; - } - } -} diff --git a/src/Notifications/Core/Models/Notification/EmailNotificationWithResult.cs b/src/Notifications/Core/Models/Notification/EmailNotificationWithResult.cs deleted file mode 100644 index a8c7e851..00000000 --- a/src/Notifications/Core/Models/Notification/EmailNotificationWithResult.cs +++ /dev/null @@ -1,46 +0,0 @@ -#nullable enable -using Altinn.Notifications.Core.Enums; -using Altinn.Notifications.Core.Models.Recipients; - -namespace Altinn.Notifications.Core.Models.Notification -{ - /// - /// An implementation of for email notifications"/> - /// Using the as recipient type and the as result type - /// - public class EmailNotificationWithResult : INotificationWithResult - { - /// - public Guid Id { get; } - - /// - public bool Succeeded { get; internal set; } - - /// - public EmailRecipient Recipient { get; } - - /// - public NotificationResult ResultStatus { get; } - - /// - /// Initializes a new instance of the class. - /// - public EmailNotificationWithResult(Guid id, EmailRecipient recipient, NotificationResult result) - { - Id = id; - Recipient = recipient; - ResultStatus = result; - } - - /// - /// Initializes a new instance of the class. - /// - internal EmailNotificationWithResult(Guid id, bool succeeded, EmailRecipient recipient, NotificationResult result) - { - Id = id; - Succeeded = succeeded; - Recipient = recipient; - ResultStatus = result; - } - } -} diff --git a/src/Notifications/Core/Models/Notification/INotification.cs b/src/Notifications/Core/Models/Notification/INotification.cs deleted file mode 100644 index 4bee1b10..00000000 --- a/src/Notifications/Core/Models/Notification/INotification.cs +++ /dev/null @@ -1,38 +0,0 @@ -#nullable enable -using System; - -using Altinn.Notifications.Core.Enums; - -namespace Altinn.Notifications.Core.Models.Notification; - -/// -/// Interface describing a base notification. -/// -public interface INotification - where TEnum : struct, Enum -{ - /// - /// Gets the id of the notification. - /// - public Guid Id { get; } - - /// - /// Gets the order id of the notification. - /// - public Guid OrderId { get; } - - /// - /// Gets the requested send time of the notification. - /// - public DateTime RequestedSendTime { get; } - - /// - /// Gets the notifiction channel for the notification. - /// - public NotificationChannel NotificationChannel { get; } - - /// - /// Gets the send result of the notification. - /// - public NotificationResult SendResult { get; } -} diff --git a/src/Notifications/Core/Models/Notification/INotificationSummary.cs b/src/Notifications/Core/Models/Notification/INotificationSummary.cs deleted file mode 100644 index 911453b2..00000000 --- a/src/Notifications/Core/Models/Notification/INotificationSummary.cs +++ /dev/null @@ -1,34 +0,0 @@ -#nullable enable -namespace Altinn.Notifications.Core.Models.Notification; - -/// -/// An interface representing a summary of the notifications related to an order -/// -public interface INotificationSummary - where TClass : class -{ - /// - /// Gets the notification order id - /// - public Guid OrderId { get; } - - /// - /// Gets the senders reference of the notification order - /// - public string? SendersReference { get; } - - /// - /// Gets the number of generated notifications - /// - public int Generated { get; } - - /// - /// Gets the number of succeeeded notifications - /// - public int Succeeded { get; } - - /// - /// Gets the list of notifications with send result - /// - public List Notifications { get; } -} diff --git a/src/Notifications/Core/Models/Notification/INotificationWithResult.cs b/src/Notifications/Core/Models/Notification/INotificationWithResult.cs deleted file mode 100644 index aa95da12..00000000 --- a/src/Notifications/Core/Models/Notification/INotificationWithResult.cs +++ /dev/null @@ -1,32 +0,0 @@ -#nullable enable -namespace Altinn.Notifications.Core.Models.Notification; - -/// -/// Interface representing a notification object with send result data -/// -/// The class representing the recipient -/// The enum used to describe the send result -public interface INotificationWithResult - where TClass : class - where TEnum : struct, Enum -{ - /// - /// Gets the notification id - /// - public Guid Id { get; } - - /// - /// Gets a boolean indicating if the sending was successful - /// - public bool Succeeded { get; } - - /// - /// Sets the recipient with contact points - /// - public TClass Recipient { get; } - - /// - /// Gets the send result - /// - public NotificationResult ResultStatus { get; } -} diff --git a/src/Notifications/Core/Models/Notification/NotificationResult.cs b/src/Notifications/Core/Models/Notification/NotificationResult.cs deleted file mode 100644 index 1f85bb3a..00000000 --- a/src/Notifications/Core/Models/Notification/NotificationResult.cs +++ /dev/null @@ -1,41 +0,0 @@ -#nullable enable -namespace Altinn.Notifications.Core.Models.Notification; - -/// -/// A class represednting a notification result -/// -public class NotificationResult - where TEnum : struct, Enum -{ - /// - /// Initializes a new instance of the class. - /// - public NotificationResult(TEnum result, DateTime resultTime) - { - ResultTime = resultTime; - Result = result; - } - - /// - /// Sets the result description - /// - public void SetResultDescription(string? description) - { - ResultDescription = description; - } - - /// - /// Gets the date and time for when the last result was set. - /// - public DateTime ResultTime { get; } - - /// - /// Gets the send result of the notification - /// - public TEnum Result { get; } - - /// - /// Gets the description of the send result - /// - public string? ResultDescription { get; private set; } -} diff --git a/src/Notifications/Core/Models/Notification/SendOperationResult.cs b/src/Notifications/Core/Models/Notification/SendOperationResult.cs deleted file mode 100644 index 2031dcaa..00000000 --- a/src/Notifications/Core/Models/Notification/SendOperationResult.cs +++ /dev/null @@ -1,85 +0,0 @@ -#nullable enable -using System.Text.Json; -using System.Text.Json.Serialization; -using Altinn.Notifications.Core.Enums; -using Altinn.Notifications.Core.Models.Orders; - -namespace Altinn.Notifications.Core.Models.Notification; - -/// -/// A class representing a send operation update object -/// -public class SendOperationResult -{ - /// - /// The notification id - /// - public Guid NotificationId { get; set; } - - /// - /// The send operation id - /// - public string OperationId { get; set; } = string.Empty; - - /// - /// The email send result - /// - public EmailNotificationResultType? SendResult { get; set; } - - /// - /// Json serializes the - /// - public string Serialize() - { - return JsonSerializer.Serialize( - this, - new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = { new JsonStringEnumConverter() } - }); - } - - /// - /// Deserialize a json string into the - /// - public static SendOperationResult? Deserialize(string serializedString) - { - return JsonSerializer.Deserialize( - serializedString, - new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true, - Converters = { new JsonStringEnumConverter() } - }); - } - - /// - /// Try to parse a json string into a - /// - public static bool TryParse(string input, out SendOperationResult value) - { - SendOperationResult? parsedOutput; - value = new SendOperationResult(); - - if (string.IsNullOrEmpty(input)) - { - return false; - } - - try - { - parsedOutput = Deserialize(input!); - - value = parsedOutput!; - return value.NotificationId != Guid.Empty; - } - catch - { - // try parse, we simply return false if fails - } - - return false; - } -} diff --git a/src/Notifications/Core/Models/NotificationTemplate/EmailTemplate.cs b/src/Notifications/Core/Models/NotificationTemplate/EmailTemplate.cs index b3b627d5..dbee9a59 100644 --- a/src/Notifications/Core/Models/NotificationTemplate/EmailTemplate.cs +++ b/src/Notifications/Core/Models/NotificationTemplate/EmailTemplate.cs @@ -1,5 +1,4 @@ -#nullable enable -using Altinn.Notifications.Core.Enums; +using Altinn.Notifications.Core.Enums; namespace Altinn.Notifications.Core.Models.NotificationTemplate; diff --git a/src/Notifications/Core/Models/NotificationTemplate/INotificationTemplate.cs b/src/Notifications/Core/Models/NotificationTemplate/INotificationTemplate.cs index 2d86c40c..5329fc08 100644 --- a/src/Notifications/Core/Models/NotificationTemplate/INotificationTemplate.cs +++ b/src/Notifications/Core/Models/NotificationTemplate/INotificationTemplate.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Altinn.Notifications.Core.Enums; diff --git a/src/Notifications/Core/Models/NotificationTemplate/SmsTemplate.cs b/src/Notifications/Core/Models/NotificationTemplate/SmsTemplate.cs index 4e79c533..9d1cb2db 100644 --- a/src/Notifications/Core/Models/NotificationTemplate/SmsTemplate.cs +++ b/src/Notifications/Core/Models/NotificationTemplate/SmsTemplate.cs @@ -1,4 +1,3 @@ -#nullable enable using Altinn.Notifications.Core.Enums; namespace Altinn.Notifications.Core.Models.NotificationTemplate; diff --git a/src/Notifications/Core/Models/Orders/IBaseNotificationOrder.cs b/src/Notifications/Core/Models/Orders/IBaseNotificationOrder.cs index 64d891c6..d90018c7 100644 --- a/src/Notifications/Core/Models/Orders/IBaseNotificationOrder.cs +++ b/src/Notifications/Core/Models/Orders/IBaseNotificationOrder.cs @@ -1,5 +1,4 @@ -#nullable enable -using Altinn.Notifications.Core.Enums; +using Altinn.Notifications.Core.Enums; namespace Altinn.Notifications.Core.Models.Orders; @@ -28,6 +27,16 @@ public interface IBaseNotificationOrder /// public NotificationChannel NotificationChannel { get; } + /// + /// Gets or sets whether notifications generated by this order should ignore KRR reservations + /// + public bool? IgnoreReservation { get; } + + /// + /// Gets or sets the id of the resource that the notification is related to + /// + public string? ResourceId { get; } + /// /// Gets the creator of the notification /// diff --git a/src/Notifications/Core/Models/Orders/NotificationOrder.cs b/src/Notifications/Core/Models/Orders/NotificationOrder.cs index 96dfee79..729ad11d 100644 --- a/src/Notifications/Core/Models/Orders/NotificationOrder.cs +++ b/src/Notifications/Core/Models/Orders/NotificationOrder.cs @@ -1,6 +1,4 @@ -#nullable enable -using System.Text.Json; -using System.Text.Json.Serialization; +using System.Text.Json; using Altinn.Notifications.Core.Enums; using Altinn.Notifications.Core.Models.NotificationTemplate; @@ -24,6 +22,12 @@ public class NotificationOrder : IBaseNotificationOrder /// > public NotificationChannel NotificationChannel { get; internal set; } + /// > + public bool? IgnoreReservation { get; internal set; } + + /// > + public string? ResourceId { get; internal set; } + /// > public Creator Creator { get; internal set; } @@ -43,7 +47,17 @@ public class NotificationOrder : IBaseNotificationOrder /// /// Initializes a new instance of the class. /// - public NotificationOrder(Guid id, string? sendersReference, List templates, DateTime requestedSendTime, NotificationChannel notificationChannel, Creator creator, DateTime created, List recipients) + public NotificationOrder( + Guid id, + string? sendersReference, + List templates, + DateTime requestedSendTime, + NotificationChannel notificationChannel, + Creator creator, + DateTime created, + List recipients, + bool? ignoreReservation, + string? resourceId) { Id = id; SendersReference = sendersReference; @@ -53,6 +67,8 @@ public NotificationOrder(Guid id, string? sendersReference, List @@ -68,14 +84,7 @@ internal NotificationOrder() /// public string Serialize() { - return JsonSerializer.Serialize( - this, - new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = { new JsonStringEnumConverter() } - }); + return JsonSerializer.Serialize(this, JsonSerializerOptionsProvider.Options); } /// @@ -83,13 +92,7 @@ public string Serialize() /// public static NotificationOrder? Deserialize(string serializedString) { - return JsonSerializer.Deserialize( - serializedString, - new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true, - Converters = { new JsonStringEnumConverter() } - }); + return JsonSerializer.Deserialize(serializedString, JsonSerializerOptionsProvider.Options); } /// diff --git a/src/Notifications/Core/Models/Orders/NotificationOrderRequest.cs b/src/Notifications/Core/Models/Orders/NotificationOrderRequest.cs index 9f794c3b..1a879f99 100644 --- a/src/Notifications/Core/Models/Orders/NotificationOrderRequest.cs +++ b/src/Notifications/Core/Models/Orders/NotificationOrderRequest.cs @@ -1,5 +1,4 @@ -#nullable enable -using Altinn.Notifications.Core.Enums; +using Altinn.Notifications.Core.Enums; using Altinn.Notifications.Core.Models.NotificationTemplate; namespace Altinn.Notifications.Core.Models.Orders; @@ -22,7 +21,7 @@ public class NotificationOrderRequest /// /// Gets the requested send time for the notification(s) /// - public DateTime RequestedSendTime { get; internal set; } + public DateTime? RequestedSendTime { get; internal set; } /// /// Gets the preferred notification channel @@ -39,10 +38,28 @@ public class NotificationOrderRequest /// public Creator Creator { get; internal set; } + /// + /// Gets a boolean indicating whether notifications generated by this order should ignore KRR reservations + /// + public bool? IgnoreReservation { get; internal set; } + + /// + /// Gets the id of the resource that the notification is related to + /// + public string? ResourceId { get; internal set; } + /// /// Initializes a new instance of the class. /// - public NotificationOrderRequest(string? sendersReference, string creatorShortName, List templates, DateTime requestedSendTime, NotificationChannel notificationChannel, List recipients) + public NotificationOrderRequest( + string? sendersReference, + string creatorShortName, + List templates, + DateTime? requestedSendTime, + NotificationChannel notificationChannel, + List recipients, + bool? ignoreReservation, + string? resourceId) { SendersReference = sendersReference; Creator = new(creatorShortName); @@ -50,6 +67,8 @@ public NotificationOrderRequest(string? sendersReference, string creatorShortNam RequestedSendTime = requestedSendTime; NotificationChannel = notificationChannel; Recipients = recipients; + IgnoreReservation = ignoreReservation; + ResourceId = resourceId; } /// diff --git a/src/Notifications/Core/Models/Orders/NotificationOrderRequestResponse.cs b/src/Notifications/Core/Models/Orders/NotificationOrderRequestResponse.cs new file mode 100644 index 00000000..5b4eb3a4 --- /dev/null +++ b/src/Notifications/Core/Models/Orders/NotificationOrderRequestResponse.cs @@ -0,0 +1,64 @@ +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Core.Models.Orders; + +/// +/// A class representing an order request response. +/// +public class NotificationOrderRequestResponse +{ + /// + /// The order id + /// + public Guid? OrderId { get; set; } + + /// + /// The recipient lookup summary + /// + public RecipientLookupResult RecipientLookup { get; set; } +} + +/// +/// Class describing a summary of recipient lookup for a notification order +/// +public class RecipientLookupResult +{ + /// + /// The lookup status + /// + [JsonPropertyName("status")] + public RecipientLookupStatus Status { get; set; } + + /// + /// List of id numbers for the recipients that are reserved + /// + [JsonPropertyName("isReserved")] + public List IsReserved { get; set; } + + /// + /// List of id numbers for the recipients where no contact points were identified + /// + [JsonPropertyName("missingContact")] + public List MissingContact { get; set; } +} + +/// +/// Enum describing the success rate for recipient lookup +/// +public enum RecipientLookupStatus +{ + /// + /// The recipient lookup was successful for all recipients + /// + Success, + + /// + /// The recipient lookup was successful for some recipients + /// + PartialSuccess, + + /// + /// The recipient lookup failed for all recipients + /// + Failed +} diff --git a/src/Notifications/Core/Models/Orders/NotificationOrderWithStatus.cs b/src/Notifications/Core/Models/Orders/NotificationOrderWithStatus.cs index 5c79f450..952e9ba4 100644 --- a/src/Notifications/Core/Models/Orders/NotificationOrderWithStatus.cs +++ b/src/Notifications/Core/Models/Orders/NotificationOrderWithStatus.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Altinn.Notifications.Core.Enums; @@ -28,6 +27,12 @@ public class NotificationOrderWithStatus : IBaseNotificationOrder /// > public NotificationChannel NotificationChannel { get; internal set; } + /// > + public bool? IgnoreReservation { get; internal set; } + + /// > + public string? ResourceId { get; internal set; } + /// /// Gets the processing status of the notication order /// @@ -41,7 +46,16 @@ public class NotificationOrderWithStatus : IBaseNotificationOrder /// /// Initializes a new instance of the class. /// - public NotificationOrderWithStatus(Guid id, string? sendersReference, DateTime requestedSendTime, Creator creator, DateTime created, NotificationChannel notificationChannel, ProcessingStatus processingStatus) + public NotificationOrderWithStatus( + Guid id, + string? sendersReference, + DateTime requestedSendTime, + Creator creator, + DateTime created, + NotificationChannel notificationChannel, + bool? ignoreReservation, + string? resourceId, + ProcessingStatus processingStatus) { Id = id; SendersReference = sendersReference; @@ -49,6 +63,8 @@ public NotificationOrderWithStatus(Guid id, string? sendersReference, DateTime r Creator = creator; Created = created; NotificationChannel = notificationChannel; + IgnoreReservation = ignoreReservation; + ResourceId = resourceId; ProcessingStatus = processingStatus; } diff --git a/src/Notifications/Core/Models/Recipient.cs b/src/Notifications/Core/Models/Recipient.cs index 5e918e77..1fc089dd 100644 --- a/src/Notifications/Core/Models/Recipient.cs +++ b/src/Notifications/Core/Models/Recipient.cs @@ -1,4 +1,5 @@ -#nullable enable +using System.Text.Json; + using Altinn.Notifications.Core.Models.Address; namespace Altinn.Notifications.Core.Models; @@ -9,9 +10,9 @@ namespace Altinn.Notifications.Core.Models; public class Recipient { /// - /// Gets the recipient's organisation number + /// Gets the recipient's organization number /// - public string? OrganisationNumber { get; set; } = null; + public string? OrganizationNumber { get; set; } = null; /// /// Gets the recipient's national identity number @@ -21,7 +22,7 @@ public class Recipient /// /// Gets or sets a value indicating whether the recipient is reserved from digital communication /// - public bool IsReserved { get; set; } + public bool? IsReserved { get; set; } /// /// Gets a list of address points for the recipient @@ -31,9 +32,9 @@ public class Recipient /// /// Initializes a new instance of the class. /// - public Recipient(List addressInfo, string? organisationNumber = null, string? nationalIdentityNumber = null) + public Recipient(List addressInfo, string? organizationNumber = null, string? nationalIdentityNumber = null) { - OrganisationNumber = organisationNumber; + OrganizationNumber = organizationNumber; NationalIdentityNumber = nationalIdentityNumber; AddressInfo = addressInfo; } @@ -44,4 +45,13 @@ public Recipient(List addressInfo, string? organisationNumber = n public Recipient() { } + + /// + /// Creates a deep copy of the recipient object + /// + internal Recipient DeepCopy() + { + string json = JsonSerializer.Serialize(this); + return JsonSerializer.Deserialize(json)!; + } } diff --git a/src/Notifications/Core/Models/Recipients/EmailRecipient.cs b/src/Notifications/Core/Models/Recipients/EmailRecipient.cs index 8d9e9216..bb99c76c 100644 --- a/src/Notifications/Core/Models/Recipients/EmailRecipient.cs +++ b/src/Notifications/Core/Models/Recipients/EmailRecipient.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Altinn.Notifications.Core.Models.Recipients; /// @@ -7,12 +6,22 @@ namespace Altinn.Notifications.Core.Models.Recipients; public class EmailRecipient { /// - /// Gets or sets the recipient id + /// Gets or sets the recipient's organization number /// - public string? RecipientId { get; set; } = null; + public string? OrganizationNumber { get; set; } = null; + + /// + /// Gets or sets the recipient's national identity number + /// + public string? NationalIdentityNumber { get; set; } = null; /// /// Gets or sets the toaddress /// public string ToAddress { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether the recipient is reserved from digital communication + /// + public bool? IsReserved { get; set; } } diff --git a/src/Notifications/Core/Models/Recipients/SmsRecipient.cs b/src/Notifications/Core/Models/Recipients/SmsRecipient.cs new file mode 100644 index 00000000..70e7cf60 --- /dev/null +++ b/src/Notifications/Core/Models/Recipients/SmsRecipient.cs @@ -0,0 +1,27 @@ +namespace Altinn.Notifications.Core.Models.Recipients; + +/// +/// Class representing an sms recipient +/// +public class SmsRecipient +{ + /// + /// Gets or sets the recipient's organization number + /// + public string? OrganizationNumber { get; set; } = null; + + /// + /// Gets or sets the recipient's national identity number + /// + public string? NationalIdentityNumber { get; set; } = null; + + /// + /// Gets or sets the mobile number + /// + public string MobileNumber { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether the recipient is reserved from digital communication + /// + public bool? IsReserved { get; set; } +} diff --git a/src/Notifications/Core/Models/Sms.cs b/src/Notifications/Core/Models/Sms.cs new file mode 100644 index 00000000..c3139cbe --- /dev/null +++ b/src/Notifications/Core/Models/Sms.cs @@ -0,0 +1,51 @@ +using System.Text.Json; + +namespace Altinn.Notifications.Core.Models; + +/// +/// Class representing an sms +/// +public class Sms +{ + /// + /// Gets or sets the id of the sms. + /// + public Guid NotificationId { get; set; } + + /// + /// Gets or sets the sender of the sms message + /// + /// + /// Can be a literal string or a phone number + /// + public string Sender { get; set; } + + /// + /// Gets or sets the recipient of the sms message + /// + public string Recipient { get; set; } + + /// + /// Gets or sets the contents of the sms message + /// + public string Message { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public Sms(Guid notificationId, string sender, string recipient, string message) + { + NotificationId = notificationId; + Recipient = recipient; + Sender = sender; + Message = message; + } + + /// + /// Json serializes the + /// + public string Serialize() + { + return JsonSerializer.Serialize(this, JsonSerializerOptionsProvider.Options); + } +} diff --git a/src/Notifications/Core/Repository/Interfaces/IOrderRepository.cs b/src/Notifications/Core/Persistence/IOrderRepository.cs similarity index 94% rename from src/Notifications/Core/Repository/Interfaces/IOrderRepository.cs rename to src/Notifications/Core/Persistence/IOrderRepository.cs index e6050b48..0b317c79 100644 --- a/src/Notifications/Core/Repository/Interfaces/IOrderRepository.cs +++ b/src/Notifications/Core/Persistence/IOrderRepository.cs @@ -1,8 +1,7 @@ -#nullable enable -using Altinn.Notifications.Core.Enums; +using Altinn.Notifications.Core.Enums; using Altinn.Notifications.Core.Models.Orders; -namespace Altinn.Notifications.Core.Repository.Interfaces; +namespace Altinn.Notifications.Core.Persistence; /// /// Interface describing all repository actions for notification orders diff --git a/src/Notifications/Core/Services/ContactPointService.cs b/src/Notifications/Core/Services/ContactPointService.cs new file mode 100644 index 00000000..68314747 --- /dev/null +++ b/src/Notifications/Core/Services/ContactPointService.cs @@ -0,0 +1,210 @@ +using Altinn.Notifications.Core.Helpers; +using Altinn.Notifications.Core.Integrations; +using Altinn.Notifications.Core.Models; +using Altinn.Notifications.Core.Models.Address; +using Altinn.Notifications.Core.Models.ContactPoints; +using Altinn.Notifications.Core.Services.Interfaces; + +namespace Altinn.Notifications.Core.Services +{ + /// + /// Implementation of the using Altinn platform services to lookup contact points + /// + public class ContactPointService : IContactPointService + { + private readonly IProfileClient _profileClient; + private readonly IRegisterClient _registerClient; + private readonly IAuthorizationService _authorizationService; + + /// + /// Initializes a new instance of the class. + /// + public ContactPointService(IProfileClient profile, IRegisterClient register, IAuthorizationService authorizationService) + { + _profileClient = profile; + _registerClient = register; + _authorizationService = authorizationService; + } + + /// + public async Task AddEmailContactPoints(List recipients, string? resourceId) + { + await AugmentRecipients( + recipients, + resourceId, + (recipient, userContactPoints) => + { + if (!string.IsNullOrEmpty(userContactPoints.Email)) + { + recipient.AddressInfo.Add(new EmailAddressPoint(userContactPoints.Email)); + } + + return recipient; + }, + (recipient, orgContactPoints) => + { + recipient.AddressInfo.AddRange(orgContactPoints.EmailList + .Select(e => new EmailAddressPoint(e)) + .ToList()); + + recipient.AddressInfo.AddRange(orgContactPoints.UserContactPoints + .Where(u => !string.IsNullOrEmpty(u.Email)) + .Select(u => new EmailAddressPoint(u.Email)) + .ToList()); + return recipient; + }); + } + + /// + public async Task AddSmsContactPoints(List recipients, string? resourceId) + { + await AugmentRecipients( + recipients, + resourceId, + (recipient, userContactPoints) => + { + if (!string.IsNullOrEmpty(userContactPoints.MobileNumber)) + { + recipient.AddressInfo.Add(new SmsAddressPoint(userContactPoints.MobileNumber)); + } + + return recipient; + }, + (recipient, orgContactPoints) => + { + recipient.AddressInfo.AddRange(orgContactPoints.MobileNumberList + .Select(m => new SmsAddressPoint(m)) + .ToList()); + + recipient.AddressInfo.AddRange(orgContactPoints.UserContactPoints + .Where(u => !string.IsNullOrEmpty(u.MobileNumber)) + .Select(u => new SmsAddressPoint(u.MobileNumber)) + .ToList()); + return recipient; + }); + } + + private async Task> AugmentRecipients( + List recipients, + string? resourceId, + Func createUserContactPoint, + Func createOrgContactPoint) + { + List augmentedRecipients = new(); + + var userLookupTask = LookupPersonContactPoints(recipients); + var orgLookupTask = LookupOrganizationContactPoints(recipients, resourceId); + await Task.WhenAll(userLookupTask, orgLookupTask); + + List userContactPointsList = userLookupTask.Result; + List organizationContactPointList = orgLookupTask.Result; + + foreach (Recipient recipient in recipients) + { + if (!string.IsNullOrEmpty(recipient.NationalIdentityNumber)) + { + UserContactPoints? userContactPoints = userContactPointsList! + .Find(u => u.NationalIdentityNumber == recipient.NationalIdentityNumber); + + if (userContactPoints != null) + { + recipient.IsReserved = userContactPoints.IsReserved; + augmentedRecipients.Add(createUserContactPoint(recipient, userContactPoints)); + } + } + else if (!string.IsNullOrEmpty(recipient.OrganizationNumber)) + { + OrganizationContactPoints? organizationContactPoints = organizationContactPointList! + .Find(o => o.OrganizationNumber == recipient.OrganizationNumber); + + if (organizationContactPoints != null) + { + augmentedRecipients.Add(createOrgContactPoint(recipient, organizationContactPoints)); + } + } + } + + return augmentedRecipients; + } + + private async Task> LookupPersonContactPoints(List recipients) + { + List nins = recipients + .Where(r => !string.IsNullOrEmpty(r.NationalIdentityNumber)) + .Select(r => r.NationalIdentityNumber!) + .ToList(); + + if (nins.Count == 0) + { + return new(); + } + + List contactPoints = await _profileClient.GetUserContactPoints(nins); + + contactPoints.ForEach(contactPoint => + { + contactPoint.MobileNumber = MobileNumberHelper.EnsureCountryCodeIfValidNumber(contactPoint.MobileNumber); + }); + + return contactPoints; + } + + private async Task> LookupOrganizationContactPoints(List recipients, string? resourceId) + { + List orgNos = recipients + .Where(r => !string.IsNullOrEmpty(r.OrganizationNumber)) + .Select(r => r.OrganizationNumber!) + .ToList(); + + if (orgNos.Count == 0) + { + return new(); + } + + Task> registerTask = _registerClient.GetOrganizationContactPoints(orgNos); + List authorizedUserContactPoints = new(); + + if (!string.IsNullOrEmpty(resourceId)) + { + var allUserContactPoints = await _profileClient.GetUserRegisteredContactPoints(orgNos, resourceId); + authorizedUserContactPoints = await _authorizationService.AuthorizeUserContactPointsForResource(allUserContactPoints, resourceId); + } + + List contactPoints = await registerTask; + + if (!string.IsNullOrEmpty(resourceId)) + { + foreach (var userContactPoint in authorizedUserContactPoints) + { + userContactPoint.UserContactPoints.ForEach(userContactPoint => + { + userContactPoint.MobileNumber = MobileNumberHelper.EnsureCountryCodeIfValidNumber(userContactPoint.MobileNumber); + }); + + var existingContactPoint = contactPoints.Find(cp => cp.OrganizationNumber == userContactPoint.OrganizationNumber); + + if (existingContactPoint != null) + { + existingContactPoint.UserContactPoints.AddRange(userContactPoint.UserContactPoints); + } + else + { + contactPoints.Add(userContactPoint); + } + } + } + + contactPoints.ForEach(contactPoint => + { + contactPoint.MobileNumberList = contactPoint.MobileNumberList + .Select(mobileNumber => + { + return MobileNumberHelper.EnsureCountryCodeIfValidNumber(mobileNumber); + }) + .ToList(); + }); + + return contactPoints; + } + } +} diff --git a/src/Notifications/Core/Services/DateTimeService.cs b/src/Notifications/Core/Services/DateTimeService.cs index a48321cd..b78bf5e7 100644 --- a/src/Notifications/Core/Services/DateTimeService.cs +++ b/src/Notifications/Core/Services/DateTimeService.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using Altinn.Notifications.Core.Services.Interfaces; diff --git a/src/Notifications/Core/Services/GuidService.cs b/src/Notifications/Core/Services/GuidService.cs index d6614803..e106fbd3 100644 --- a/src/Notifications/Core/Services/GuidService.cs +++ b/src/Notifications/Core/Services/GuidService.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using Altinn.Notifications.Core.Services.Interfaces; diff --git a/src/Notifications/Core/Services/Interfaces/IContactPointService.cs b/src/Notifications/Core/Services/Interfaces/IContactPointService.cs new file mode 100644 index 00000000..48f21384 --- /dev/null +++ b/src/Notifications/Core/Services/Interfaces/IContactPointService.cs @@ -0,0 +1,28 @@ +using Altinn.Notifications.Core.Models; + +namespace Altinn.Notifications.Core.Services.Interfaces +{ + /// + /// Service for retrieving contact points for recipients + /// + public interface IContactPointService + { + /// + /// Looks up and adds the email contact points for recipients based on their national identity number or organization number + /// + /// List of recipients to retrieve contact points for + /// The resource to find contact points in relation to + /// The list of recipients augumented with email address points where available + /// Implementation alters the recipient reference object directly + public Task AddEmailContactPoints(List recipients, string? resourceId); + + /// + /// Looks up and adds the SMS contact points for recipients based on their national identity number or organization number + /// + /// List of recipients to retrieve contact points for + /// The resource to find contact points in relation to + /// The list of recipients augumented with SMS address points where available + /// Implementation alters the recipient reference object directly + public Task AddSmsContactPoints(List recipients, string? resourceId); + } +} diff --git a/src/Notifications/Core/Services/Interfaces/IDateTimeService.cs b/src/Notifications/Core/Services/Interfaces/IDateTimeService.cs index 8ff6b2b1..9cdae256 100644 --- a/src/Notifications/Core/Services/Interfaces/IDateTimeService.cs +++ b/src/Notifications/Core/Services/Interfaces/IDateTimeService.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Altinn.Notifications.Core.Services.Interfaces; +namespace Altinn.Notifications.Core.Services.Interfaces; /// /// Interface describing a dateTime service diff --git a/src/Notifications/Core/Services/Interfaces/IGuidService.cs b/src/Notifications/Core/Services/Interfaces/IGuidService.cs index 16b81efc..04b70707 100644 --- a/src/Notifications/Core/Services/Interfaces/IGuidService.cs +++ b/src/Notifications/Core/Services/Interfaces/IGuidService.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Altinn.Notifications.Core.Services.Interfaces; +namespace Altinn.Notifications.Core.Services.Interfaces; /// /// Interface describing a guid service diff --git a/src/Notifications/Core/Services/Interfaces/IOrderRequestService.cs b/src/Notifications/Core/Services/Interfaces/IOrderRequestService.cs index cf9350e1..361eb6cd 100644 --- a/src/Notifications/Core/Services/Interfaces/IOrderRequestService.cs +++ b/src/Notifications/Core/Services/Interfaces/IOrderRequestService.cs @@ -1,5 +1,4 @@ -#nullable enable -using Altinn.Notifications.Core.Models.Orders; +using Altinn.Notifications.Core.Models.Orders; namespace Altinn.Notifications.Core.Services.Interfaces; @@ -12,6 +11,6 @@ public interface IOrderRequestService /// Registers a new order /// /// The notification order request - /// The registered notification order - public Task RegisterNotificationOrder(NotificationOrderRequest orderRequest); + /// The order request response object + public Task RegisterNotificationOrder(NotificationOrderRequest orderRequest); } diff --git a/src/Notifications/Core/Services/OrderRequestService.cs b/src/Notifications/Core/Services/OrderRequestService.cs index 57b891a4..4d41f103 100644 --- a/src/Notifications/Core/Services/OrderRequestService.cs +++ b/src/Notifications/Core/Services/OrderRequestService.cs @@ -1,7 +1,9 @@ using Altinn.Notifications.Core.Configuration; +using Altinn.Notifications.Core.Enums; +using Altinn.Notifications.Core.Models; using Altinn.Notifications.Core.Models.NotificationTemplate; using Altinn.Notifications.Core.Models.Orders; -using Altinn.Notifications.Core.Repository.Interfaces; +using Altinn.Notifications.Core.Persistence; using Altinn.Notifications.Core.Services.Interfaces; using Microsoft.Extensions.Options; @@ -14,6 +16,7 @@ namespace Altinn.Notifications.Core.Services; public class OrderRequestService : IOrderRequestService { private readonly IOrderRepository _repository; + private readonly IContactPointService _contactPointService; private readonly IGuidService _guid; private readonly IDateTimeService _dateTime; private readonly string _defaultEmailFromAddress; @@ -22,9 +25,15 @@ public class OrderRequestService : IOrderRequestService /// /// Initializes a new instance of the class. /// - public OrderRequestService(IOrderRepository repository, IGuidService guid, IDateTimeService dateTime, IOptions config) + public OrderRequestService( + IOrderRepository repository, + IContactPointService contactPointService, + IGuidService guid, + IDateTimeService dateTime, + IOptions config) { _repository = repository; + _contactPointService = contactPointService; _guid = guid; _dateTime = dateTime; _defaultEmailFromAddress = config.Value.DefaultEmailFromAddress; @@ -32,10 +41,12 @@ public OrderRequestService(IOrderRepository repository, IGuidService guid, IDate } /// - public async Task RegisterNotificationOrder(NotificationOrderRequest orderRequest) + public async Task RegisterNotificationOrder(NotificationOrderRequest orderRequest) { Guid orderId = _guid.NewGuid(); - DateTime created = _dateTime.UtcNow(); + DateTime currentime = _dateTime.UtcNow(); + + var lookupResult = await GetRecipientLookupResult(orderRequest.Recipients, orderRequest.NotificationChannel, orderRequest.ResourceId); var templates = SetSenderIfNotDefined(orderRequest.Templates); @@ -43,15 +54,79 @@ public async Task RegisterNotificationOrder(NotificationOrder orderId, orderRequest.SendersReference, templates, - orderRequest.RequestedSendTime, + orderRequest.RequestedSendTime ?? currentime, orderRequest.NotificationChannel, orderRequest.Creator, - created, - orderRequest.Recipients); + currentime, + orderRequest.Recipients, + orderRequest.IgnoreReservation, + orderRequest.ResourceId); NotificationOrder savedOrder = await _repository.Create(order); - return savedOrder; + return new NotificationOrderRequestResponse() + { + OrderId = savedOrder.Id, + RecipientLookup = lookupResult + }; + } + + private async Task GetRecipientLookupResult(List originalRecipients, NotificationChannel channel, string? resourceId) + { + List recipientsWithoutContactPoint = new(); + + foreach (var recipient in originalRecipients) + { + if (channel == NotificationChannel.Email && !recipient.AddressInfo.Exists(ap => ap.AddressType == AddressType.Email)) + { + recipientsWithoutContactPoint.Add(recipient.DeepCopy()); + } + else if (channel == NotificationChannel.Sms && !recipient.AddressInfo.Exists(ap => ap.AddressType == AddressType.Sms)) + { + recipientsWithoutContactPoint.Add(recipient.DeepCopy()); + } + } + + if (recipientsWithoutContactPoint.Count == 0) + { + return null; + } + + if (channel == NotificationChannel.Email) + { + await _contactPointService.AddEmailContactPoints(recipientsWithoutContactPoint, resourceId); + } + else if (channel == NotificationChannel.Sms) + { + await _contactPointService.AddSmsContactPoints(recipientsWithoutContactPoint, resourceId); + } + + var isReserved = recipientsWithoutContactPoint.Where(r => r.IsReserved.HasValue && r.IsReserved.Value).Select(r => r.NationalIdentityNumber!).ToList(); + + RecipientLookupResult lookupResult = new() + { + IsReserved = isReserved, + MissingContact = recipientsWithoutContactPoint + .Where(r => channel == NotificationChannel.Email ? + !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Email) : + !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Sms)) + .Select(r => r.OrganizationNumber ?? r.NationalIdentityNumber!) + .Except(isReserved) + .ToList() + }; + + int recipientsWeCannotReach = lookupResult.MissingContact.Union(lookupResult.IsReserved).ToList().Count; + + if (recipientsWeCannotReach == recipientsWithoutContactPoint.Count) + { + lookupResult.Status = RecipientLookupStatus.Failed; + } + else if (recipientsWeCannotReach > 0) + { + lookupResult.Status = RecipientLookupStatus.PartialSuccess; + } + + return lookupResult; } private List SetSenderIfNotDefined(List templates) diff --git a/src/Notifications/LocalTestNotifications/LocalAuthorizationService.cs b/src/Notifications/LocalTestNotifications/LocalAuthorizationService.cs new file mode 100644 index 00000000..679366f0 --- /dev/null +++ b/src/Notifications/LocalTestNotifications/LocalAuthorizationService.cs @@ -0,0 +1,13 @@ +using Altinn.Notifications.Core.Integrations; +using Altinn.Notifications.Core.Models.ContactPoints; + +namespace LocalTest.Notifications.LocalTestNotifications +{ + public class LocalAuthorizationService : IAuthorizationService + { + public Task> AuthorizeUserContactPointsForResource(List organizationContactPoints, string resourceId) + { + return Task.FromResult(organizationContactPoints); + } + } +} diff --git a/src/Notifications/LocalTestNotifications/LocalOrderRepository.cs b/src/Notifications/LocalTestNotifications/LocalOrderRepository.cs index 4b3cab55..899ffa1d 100644 --- a/src/Notifications/LocalTestNotifications/LocalOrderRepository.cs +++ b/src/Notifications/LocalTestNotifications/LocalOrderRepository.cs @@ -3,7 +3,7 @@ using Altinn.Notifications.Core.Enums; using Altinn.Notifications.Core.Models.Orders; -using Altinn.Notifications.Core.Repository.Interfaces; +using Altinn.Notifications.Core.Persistence; using LocalTest.Configuration; diff --git a/src/Notifications/LocalTestNotifications/LocalProfileClient.cs b/src/Notifications/LocalTestNotifications/LocalProfileClient.cs new file mode 100644 index 00000000..a1f6f35b --- /dev/null +++ b/src/Notifications/LocalTestNotifications/LocalProfileClient.cs @@ -0,0 +1,47 @@ +using Altinn.Notifications.Core.Integrations; +using Altinn.Notifications.Core.Models.ContactPoints; + +using LocalTest.Services.TestData; + +namespace LocalTest.Notifications.LocalTestNotifications +{ + public class LocalProfileClient : IProfileClient + { + private readonly TestDataService _testDataService; + + public LocalProfileClient(TestDataService testDataService) + { + _testDataService = testDataService; + } + + public async Task> GetUserContactPoints(List nationalIdentityNumbers) + { + List contactPoints = new(); + var data = await _testDataService.GetTestData(); + + + contactPoints.AddRange(data.Profile.User + .Where(u => nationalIdentityNumbers.Contains(u.Value.Party.SSN)) + .Select(u => + { + var user = u.Value; + return new UserContactPoints() + { + NationalIdentityNumber = user.Party.SSN, + Email = user.Email, + MobileNumber = user.PhoneNumber + }; + }) + .ToList()); + + return contactPoints; + + } + + public async Task> GetUserRegisteredContactPoints(List organizationNumbers, string resourceId) + { + await Task.CompletedTask; + return new List(); + } + } +} diff --git a/src/Notifications/LocalTestNotifications/LocalRegisterClient.cs b/src/Notifications/LocalTestNotifications/LocalRegisterClient.cs new file mode 100644 index 00000000..d8d92cc6 --- /dev/null +++ b/src/Notifications/LocalTestNotifications/LocalRegisterClient.cs @@ -0,0 +1,41 @@ +using Altinn.Notifications.Core.Integrations; +using Altinn.Notifications.Core.Models.ContactPoints; + +using LocalTest.Services.TestData; + +namespace LocalTest.Notifications.LocalTestNotifications +{ + public class LocalRegisterClient : IRegisterClient + { + private readonly TestDataService _testDataService; + + public LocalRegisterClient(TestDataService testDataService) + { + _testDataService = testDataService; + } + + public async Task> GetOrganizationContactPoints(List organizationNumbers) + { + var data = await _testDataService.GetTestData(); + + List orgContactPoints = new(); + + + orgContactPoints.AddRange(data.Register.Org + .Where(o => organizationNumbers.Contains(o.Value.OrgNumber)) + .Select(o => + { + var organization = o.Value; + return new OrganizationContactPoints() + { + OrganizationNumber = organization.OrgNumber, + EmailList = new List() { organization.EMailAddress }, + MobileNumberList = new List() { organization.MobileNumber } + }; + }) + .ToList()); + + return orgContactPoints; + } + } +} diff --git a/src/Notifications/LocalTestNotifications/NotificationsServiceExtentions.cs b/src/Notifications/LocalTestNotifications/NotificationsServiceExtentions.cs index 2fb5392a..43119522 100644 --- a/src/Notifications/LocalTestNotifications/NotificationsServiceExtentions.cs +++ b/src/Notifications/LocalTestNotifications/NotificationsServiceExtentions.cs @@ -1,31 +1,39 @@ using Altinn.Notifications.Core.Configuration; -using Altinn.Notifications.Core.Repository.Interfaces; +using Altinn.Notifications.Core.Integrations; +using Altinn.Notifications.Core.Persistence; using Altinn.Notifications.Core.Services; using Altinn.Notifications.Core.Services.Interfaces; using Altinn.Notifications.Extensions; using Altinn.Notifications.Models; using Altinn.Notifications.Validators; + using FluentValidation; + using LocalTest.Notifications.Persistence.Repository; namespace LocalTest.Notifications.LocalTestNotifications; public static class NotificationsServiceExtentions { - public static void AddNotificationServices(this IServiceCollection services, string baseUrl) + public static void AddNotificationServices(this IServiceCollection services, string baseUrl, IConfiguration config) { // Notifications services ValidatorOptions.Global.LanguageManager.Enabled = false; ResourceLinkExtensions.Initialize(baseUrl); services.Configure((c) => c.DefaultEmailFromAddress = "localtest@altinn.no"); - + services .AddSingleton, EmailNotificationOrderRequestValidator>() .AddSingleton, SmsNotificationOrderRequestValidator>() .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); } } \ No newline at end of file diff --git a/src/Services/Register/Implementation/PartiesWrapper.cs b/src/Services/Register/Implementation/PartiesWrapper.cs index ac0611c8..8412c59a 100644 --- a/src/Services/Register/Implementation/PartiesWrapper.cs +++ b/src/Services/Register/Implementation/PartiesWrapper.cs @@ -27,6 +27,7 @@ public PartiesWrapper( public async Task GetParty(int partyId) { var data = await _testDataService.GetTestData(); + Party? party = data.Register.Party.TryGetValue(partyId.ToString()!, out var value) ? value : null; await AddPersonOrOrganization(party); diff --git a/src/Startup.cs b/src/Startup.cs index 1e58a60d..03ab99d8 100644 --- a/src/Startup.cs +++ b/src/Startup.cs @@ -125,7 +125,7 @@ public void ConfigureServices(IServiceCollection services) // Notifications services GeneralSettings generalSettings = Configuration.GetSection("GeneralSettings").Get(); - services.AddNotificationServices(generalSettings.BaseUrl); + services.AddNotificationServices(generalSettings.BaseUrl, Configuration); // Storage services services.AddSingleton(); diff --git a/testdata/Profile/User/1001.json b/testdata/Profile/User/1001.json index c36a9785..37715201 100644 --- a/testdata/Profile/User/1001.json +++ b/testdata/Profile/User/1001.json @@ -4,7 +4,10 @@ "PhoneNumber": "+4790000000", "Email": "test@test.com", "PartyId": 510001, - "Party": {}, + "Party": { + "partyId": "510001", + "ssn": "01899699552" + }, "UserType": 0, "ProfileSettingPreference": { "Language": "nb", diff --git a/testdata/Profile/User/1002.json b/testdata/Profile/User/1002.json index a7767b65..5ab7f9d3 100644 --- a/testdata/Profile/User/1002.json +++ b/testdata/Profile/User/1002.json @@ -4,7 +4,10 @@ "PhoneNumber": "12345678", "Email": "test@test.com", "PartyId": 510002, - "Party": {}, + "Party": { + "partyId": "510002", + "ssn": "17858296439" + }, "UserType": 0, "ProfileSettingPreference": { "Language": "nb", diff --git a/testdata/Profile/User/1003.json b/testdata/Profile/User/1003.json index b04e2df1..511e5594 100644 --- a/testdata/Profile/User/1003.json +++ b/testdata/Profile/User/1003.json @@ -4,10 +4,13 @@ "PhoneNumber": "12345678", "Email": "test@test.com", "PartyId": 510003, - "Party": {}, + "Party": { + "partyId": "510003", + "ssn": "08829698278" + }, "UserType": 0, "ProfileSettingPreference": { "Language": "nb", "doNotPromptForParty": true } -} +} \ No newline at end of file diff --git a/testdata/Profile/User/12345.json b/testdata/Profile/User/12345.json index d1ad5db1..1033c855 100644 --- a/testdata/Profile/User/12345.json +++ b/testdata/Profile/User/12345.json @@ -4,10 +4,13 @@ "PhoneNumber": "12345678", "Email": "test@test.com", "PartyId": 512345, - "Party": {}, + "Party": { + "partyId": "512345", + "ssn": "01017512345" + }, "UserType": 0, "ProfileSettingPreference": { "Language": "nb", "doNotPromptForParty": true } -} +} \ No newline at end of file diff --git a/testdata/Profile/User/1337.json b/testdata/Profile/User/1337.json index 7624c62b..0f7e15b7 100644 --- a/testdata/Profile/User/1337.json +++ b/testdata/Profile/User/1337.json @@ -4,10 +4,13 @@ "PhoneNumber": "90001337", "Email": "1337@altinnstudiotestusers.com", "PartyId": 501337, - "Party": {}, + "Party": { + "partyId": "501337", + "ssn": "01039012345" + }, "UserType": 1, "ProfileSettingPreference": { "Language": "nn", "doNotPromptForParty": true } -} +} \ No newline at end of file diff --git a/testdata/Register/Person/01899699552.json b/testdata/Register/Person/01899699552.json index d9979c89..e35b04b8 100644 --- a/testdata/Register/Person/01899699552.json +++ b/testdata/Register/Person/01899699552.json @@ -5,7 +5,7 @@ "MiddleName": "", "LastName": "Partner", "TelephoneNumber": "12345678", - "MobileNumber": "87654321", + "MobileNumber": "+4799315829", "MailingAddress": "Tuftekåsvegen 7 3920 PORSGRUNN", "MailingPostalCode": "3920", "MailingPostalCity": "PORSGRUNN",