diff --git a/src/Configuration/LocalPlatformSettings.cs b/src/Configuration/LocalPlatformSettings.cs index 8b3aadcb..e7d540cc 100644 --- a/src/Configuration/LocalPlatformSettings.cs +++ b/src/Configuration/LocalPlatformSettings.cs @@ -10,7 +10,7 @@ public class LocalPlatformSettings string _localTestDataPath = null; /// - /// The endpoint for the bridge + /// The path to the local storage folder /// public string LocalTestingStorageBasePath { get; set; } @@ -21,12 +21,16 @@ public class LocalPlatformSettings public string BlobStorageFolder { get; set; } = "blobs/"; + public string NotificationsStorageFolder { get; set; } = "notifications/"; + /// /// Folder where static test data like profile, authorization, and register data is available for local testing. /// - public string LocalTestingStaticTestDataPath { + public string LocalTestingStaticTestDataPath + { get => _localTestDataPath; - set { + set + { if (!value.EndsWith(Path.DirectorySeparatorChar) && !value.EndsWith(Path.AltDirectorySeparatorChar)) { @@ -47,7 +51,7 @@ public string LocalTestingStaticTestDataPath { /// public string LocalAppMode { get; set; } - public string DocumentDbFolder { get; set; } = "documentdb/"; + public string DocumentDbFolder { get; set; } = "documentdb/"; public string InstanceCollectionFolder { get; set; } = "instances/"; diff --git a/src/LocalTest.csproj b/src/LocalTest.csproj index eae18855..0932a0e4 100644 --- a/src/LocalTest.csproj +++ b/src/LocalTest.csproj @@ -4,6 +4,9 @@ net6.0 56f36ce2-b44b-415e-a8a5-f399a76e35b9 enable + + + $(DefineConstants);LOCALTEST @@ -12,6 +15,7 @@ + diff --git a/src/Notifications/API/Controllers/EmailNotificationOrdersController.cs b/src/Notifications/API/Controllers/EmailNotificationOrdersController.cs new file mode 100644 index 00000000..aa055931 --- /dev/null +++ b/src/Notifications/API/Controllers/EmailNotificationOrdersController.cs @@ -0,0 +1,98 @@ +#nullable enable +#if !LOCALTEST +using Altinn.Notifications.Configuration; +#endif +using Altinn.Notifications.Core.Models; +using Altinn.Notifications.Core.Models.Orders; +using Altinn.Notifications.Core.Services.Interfaces; +using Altinn.Notifications.Extensions; +using Altinn.Notifications.Mappers; +using Altinn.Notifications.Models; +using Altinn.Notifications.Validators; + +using FluentValidation; + +#if !LOCALTEST +using Microsoft.AspNetCore.Authorization; +#endif +using Microsoft.AspNetCore.Mvc; + +#if !LOCALTEST +using Swashbuckle.AspNetCore.Annotations; +using Swashbuckle.AspNetCore.Filters; +#endif + +namespace Altinn.Notifications.Controllers; + +/// +/// Controller for all operations related to email notification orders +/// +[Route("notifications/api/v1/orders/email")] +[ApiController] +#if !LOCALTEST +[Authorize(Policy = AuthorizationConstants.POLICY_CREATE_SCOPE_OR_PLATFORM_ACCESS)] +[SwaggerResponse(401, "Caller is unauthorized")] +[SwaggerResponse(403, "Caller is not authorized to access the requested resource")] +# endif +public class EmailNotificationOrdersController : ControllerBase +{ + private readonly IValidator _validator; + private readonly IEmailNotificationOrderService _orderService; + + /// + /// Initializes a new instance of the class. + /// + public EmailNotificationOrdersController(IValidator validator, IEmailNotificationOrderService orderService) + { + _validator = validator; + _orderService = orderService; + } + + /// + /// Add an email notification order. + /// + /// + /// The API will accept the request after som basic validation of the request. + /// The system will also attempt to verify that it will be possible to fulfill the order. + /// + /// The id of the registered notification order + [HttpPost] + [Consumes("application/json")] + [Produces("application/json")] +#if !LOCALTEST + [SwaggerResponse(202, "The notification order was accepted", typeof(OrderIdExt))] + [SwaggerResponse(400, "The notification order is invalid", typeof(ValidationProblemDetails))] + [SwaggerResponseHeader(202, "Location", "string", "Link to access the newly created notification order.")] +#endif + public async Task> Post(EmailNotificationOrderRequestExt emailNotificationOrderRequest) + { + var validationResult = _validator.Validate(emailNotificationOrderRequest); + if (!validationResult.IsValid) + { + validationResult.AddToModelState(ModelState); + return ValidationProblem(ModelState); + } + +#if LOCALTEST + string creator = "localtest"; +#else + string? creator = HttpContext.GetOrg(); + + if (creator == null) + { + return Forbid(); + } +#endif + + var orderRequest = emailNotificationOrderRequest.MapToOrderRequest(creator); + (NotificationOrder? registeredOrder, ServiceError? error) = await _orderService.RegisterEmailNotificationOrder(orderRequest); + + if (error != null) + { + return StatusCode(error.ErrorCode, error.ErrorMessage); + } + + string selfLink = registeredOrder!.GetSelfLink(); + return Accepted(selfLink, new OrderIdExt(registeredOrder!.Id)); + } +} \ No newline at end of file diff --git a/src/Notifications/API/Extensions/ResourceLinkExtensions.cs b/src/Notifications/API/Extensions/ResourceLinkExtensions.cs new file mode 100644 index 00000000..a8ce73fa --- /dev/null +++ b/src/Notifications/API/Extensions/ResourceLinkExtensions.cs @@ -0,0 +1,81 @@ +#nullable enable +using Altinn.Notifications.Core.Models.Orders; +using Altinn.Notifications.Models; + +namespace Altinn.Notifications.Extensions; + +/// +/// Extension class for ResourceLinks +/// +public static class ResourceLinkExtensions +{ + private static string? _baseUri; + + /// + /// Initializes the ResourceLinkExtensions with the base URI from settings. + /// + /// + /// Should be called during startup to ensure base url is set + /// + public static void Initialize(string baseUri) + { + _baseUri = baseUri; + } + + /// + /// Sets the resource links on an external notification order + /// + /// Exception if class has not been initialized in Program.cs + public static void SetResourceLinks(this NotificationOrderExt order) + { + if (_baseUri == null) + { + throw new InvalidOperationException("ResourceLinkExtensions has not been initialized with the base URI."); + } + + string self = _baseUri + "/notifications/api/v1/orders/" + order.Id; + + order.Links = new() + { + Self = self, + Status = self + "/status", + Notifications = self + "/notifications" + }; + } + + /// + /// Gets the self link for the provided notification order + /// + /// Exception if class has not been initialized in Program.cs + public static void NotificationSummaryResourceLinks(this NotificationOrderWithStatusExt order) + { + if (_baseUri == null) + { + throw new InvalidOperationException("ResourceLinkExtensions has not been initialized with the base URI."); + } + + string baseUri = $"{_baseUri}/notifications/api/v1/orders/{order!.Id}/notifications/"; + + if (order.NotificationsStatusSummary?.Email != null) + { + order.NotificationsStatusSummary.Email.Links = new() + { + Self = baseUri + "email" + }; + } + } + + /// + /// 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) + { + if (_baseUri == null) + { + throw new InvalidOperationException("ResourceLinkExtensions has not been initialized with the base URI."); + } + + return _baseUri + "/notifications/api/v1/orders/" + order!.Id; + } +} diff --git a/src/Notifications/API/Mappers/OrderMapper.cs b/src/Notifications/API/Mappers/OrderMapper.cs new file mode 100644 index 00000000..c0738e15 --- /dev/null +++ b/src/Notifications/API/Mappers/OrderMapper.cs @@ -0,0 +1,168 @@ +#nullable enable +using Altinn.Notifications.Core.Enums; +using Altinn.Notifications.Core.Models; +using Altinn.Notifications.Core.Models.Address; +using Altinn.Notifications.Core.Models.NotificationTemplate; +using Altinn.Notifications.Core.Models.Orders; +using Altinn.Notifications.Extensions; +using Altinn.Notifications.Models; + +namespace Altinn.Notifications.Mappers; + +/// +/// Mapper for +/// +public static class OrderMapper +{ + /// + /// Maps a to a + /// + public static NotificationOrderRequest MapToOrderRequest(this EmailNotificationOrderRequestExt extRequest, string creator) + { + var emailTemplate = new EmailTemplate(null, extRequest.Subject, extRequest.Body, (EmailContentType)extRequest.ContentType); + + var recipients = new List(); + + recipients.AddRange( + extRequest.Recipients.Select(r => new Recipient(string.Empty, new List() { new EmailAddressPoint(r.EmailAddress!) }))); + + return new NotificationOrderRequest( + extRequest.SendersReference, + creator, + new List() { emailTemplate }, + extRequest.RequestedSendTime, + NotificationChannel.Email, + recipients); + } + + /// + /// Maps a to a + /// + public static NotificationOrderExt MapToNotificationOrderExt(this NotificationOrder order) + { + var orderExt = new NotificationOrderExt(); + + orderExt.MapBaseNotificationOrder(order); + orderExt.Recipients = order.Recipients.MapToRecipientExt(); + + foreach (var template in order.Templates) + { + switch (template.Type) + { + case NotificationTemplateType.Email: + var emailTemplate = template! as EmailTemplate; + + orderExt.EmailTemplate = new() + { + Body = emailTemplate!.Body, + FromAddress = emailTemplate.FromAddress, + ContentType = (EmailContentTypeExt)emailTemplate.ContentType, + Subject = emailTemplate.Subject + }; + + break; + default: + break; + } + } + + orderExt.SetResourceLinks(); + return orderExt; + } + + /// + /// Maps a to a + /// + public static NotificationOrderWithStatusExt MapToNotificationOrderWithStatusExt(this NotificationOrderWithStatus order) + { + var orderExt = new NotificationOrderWithStatusExt(); + orderExt.MapBaseNotificationOrder(order); + + orderExt.ProcessingStatus = new() + { + LastUpdate = order.ProcessingStatus.LastUpdate, + Status = order.ProcessingStatus.Status.ToString(), + StatusDescription = order.ProcessingStatus.StatusDescription + }; + + if (order.NotificationStatuses.Any()) + { + orderExt.NotificationsStatusSummary = new(); + foreach (var entry in order.NotificationStatuses) + { + NotificationTemplateType notificationType = entry.Key; + NotificationStatus status = entry.Value; + + switch (notificationType) + { + case NotificationTemplateType.Email: + orderExt.NotificationsStatusSummary.Email = new() + { + Generated = status.Generated, + Succeeded = status.Succeeded + }; + break; + } + } + + orderExt.NotificationSummaryResourceLinks(); + } + + return orderExt; + } + + /// + /// Maps a list of to a + /// + public static NotificationOrderListExt MapToNotificationOrderListExt(this List orders) + { + NotificationOrderListExt ordersExt = new() + { + Count = orders.Count + }; + + foreach (NotificationOrder order in orders) + { + ordersExt.Orders.Add(order.MapToNotificationOrderExt()); + } + + return ordersExt; + } + + /// + /// Maps a List of to a List of + /// + internal static List MapToRecipientExt(this List recipients) + { + var recipientExt = new List(); + + recipientExt.AddRange( + recipients.Select(r => new RecipientExt + { + EmailAddress = GetEmailFromAddressList(r.AddressInfo) + })); + + return recipientExt; + } + + private static IBaseNotificationOrderExt MapBaseNotificationOrder(this IBaseNotificationOrderExt orderExt, IBaseNotificationOrder order) + { + orderExt.Id = order.Id.ToString(); + orderExt.SendersReference = order.SendersReference; + orderExt.Created = order.Created; + orderExt.Creator = order.Creator.ShortName; + orderExt.NotificationChannel = (NotificationChannelExt)order.NotificationChannel; + orderExt.RequestedSendTime = order.RequestedSendTime; + + return orderExt; + } + + private static string? GetEmailFromAddressList(List addressPoints) + { + var emailAddressPoint = addressPoints + .Find(a => a.AddressType.Equals(AddressType.Email)) + as EmailAddressPoint; + + return emailAddressPoint?.EmailAddress; + } +} diff --git a/src/Notifications/API/Models/EmailContentTypeExt.cs b/src/Notifications/API/Models/EmailContentTypeExt.cs new file mode 100644 index 00000000..22921275 --- /dev/null +++ b/src/Notifications/API/Models/EmailContentTypeExt.cs @@ -0,0 +1,18 @@ +#nullable enable +namespace Altinn.Notifications.Models; + +/// +/// Enum describing available content types for an email. +/// +public enum EmailContentTypeExt +{ + /// + /// The email format is plain text. + /// + Plain, + + /// + /// The email contains HTML elements + /// + Html +} diff --git a/src/Notifications/API/Models/EmailNotificationOrderRequestExt.cs b/src/Notifications/API/Models/EmailNotificationOrderRequestExt.cs new file mode 100644 index 00000000..20153a7e --- /dev/null +++ b/src/Notifications/API/Models/EmailNotificationOrderRequestExt.cs @@ -0,0 +1,58 @@ +#nullable enable +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Models; + +/// +/// Class representing an email notiication order request +/// +/// +/// External representaion to be used in the API. +/// +public class EmailNotificationOrderRequestExt +{ + /// + /// Gets or sets the subject of the email + /// + [JsonPropertyName("subject")] + public string Subject { get; set; } = string.Empty; + + /// + /// Gets or sets the body of the email + /// + [JsonPropertyName("body")] + 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(); + + /// + /// Json serialized the + /// + public string Serialize() + { + return JsonSerializer.Serialize(this); + } +} diff --git a/src/Notifications/API/Models/EmailNotificationSummaryExt.cs b/src/Notifications/API/Models/EmailNotificationSummaryExt.cs new file mode 100644 index 00000000..99db078a --- /dev/null +++ b/src/Notifications/API/Models/EmailNotificationSummaryExt.cs @@ -0,0 +1,44 @@ +#nullable enable +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Core.Models.Notification +{ + /// + /// A class representing an email notification summary + /// + /// + /// External representaion to be used in the API. + /// + public class EmailNotificationSummaryExt + { + /// + /// The order id + /// + [JsonPropertyName("orderId")] + public Guid OrderId { get; set; } + + /// + /// The senders reference + /// + [JsonPropertyName("sendersReference")] + public string? SendersReference { get; set; } + + /// + /// The number of generated email notifications + /// + [JsonPropertyName("generated")] + public int Generated { get; set; } + + /// + /// The number of email 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 List(); + } +} diff --git a/src/Notifications/API/Models/EmailNotificationWithResultExt.cs b/src/Notifications/API/Models/EmailNotificationWithResultExt.cs new file mode 100644 index 00000000..2e664978 --- /dev/null +++ b/src/Notifications/API/Models/EmailNotificationWithResultExt.cs @@ -0,0 +1,37 @@ +#nullable enable +using System.Text.Json.Serialization; + +using Altinn.Notifications.Models; + +namespace Altinn.Notifications.Core.Models.Notification +{ + /// + /// EmailNotificationWithResultExt class + /// + public class EmailNotificationWithResultExt + { + /// + /// 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/EmailTemplateExt.cs b/src/Notifications/API/Models/EmailTemplateExt.cs new file mode 100644 index 00000000..8e2c40c4 --- /dev/null +++ b/src/Notifications/API/Models/EmailTemplateExt.cs @@ -0,0 +1,34 @@ +#nullable enable +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Models; + +/// +/// Template for an email notification +/// +public class EmailTemplateExt +{ + /// + /// Gets the from adress of emails created by the template + /// + [JsonPropertyName("fromAddress")] + public string FromAddress { get; set; } = string.Empty; + + /// + /// Gets the subject of emails created by the template + /// + [JsonPropertyName("subject")] + public string Subject { get; set; } = string.Empty; + + /// + /// Gets the body of emails created by the template + /// + [JsonPropertyName("body")] + public string Body { get; set; } = string.Empty; + + /// + /// Gets the content type of emails created by the template + /// + [JsonPropertyName("contentType")] + public EmailContentTypeExt ContentType { get; set; } = EmailContentTypeExt.Plain; +} diff --git a/src/Notifications/API/Models/IBaseNotificationOrderExt.cs b/src/Notifications/API/Models/IBaseNotificationOrderExt.cs new file mode 100644 index 00000000..4d7f2065 --- /dev/null +++ b/src/Notifications/API/Models/IBaseNotificationOrderExt.cs @@ -0,0 +1,41 @@ +#nullable enable +namespace Altinn.Notifications.Models; + +/// +/// A class representing the base properties of a registered notification order. +/// +/// +/// External representaion to be used in the API. +/// +public interface IBaseNotificationOrderExt +{ + /// + /// Gets or sets the id of the notification order + /// + public string Id { get; set; } + + /// + /// Gets or sets the short name of the creator of the notification order + /// + public string Creator { get; set; } + + /// + /// Gets or sets the senders reference of the notification + /// + public string? SendersReference { get; set; } + + /// + /// Gets or sets the requested send time of the notification + /// + public DateTime RequestedSendTime { get; set; } + + /// + /// Gets or sets the date and time of when the notification order was created + /// + public DateTime Created { get; set; } + + /// + /// Gets or sets the preferred notification channel of the notification order + /// + public NotificationChannelExt NotificationChannel { get; set; } +} diff --git a/src/Notifications/API/Models/NotificationChannelExt.cs b/src/Notifications/API/Models/NotificationChannelExt.cs new file mode 100644 index 00000000..11bea3a2 --- /dev/null +++ b/src/Notifications/API/Models/NotificationChannelExt.cs @@ -0,0 +1,13 @@ +#nullable enable +namespace Altinn.Notifications.Models; + +/// +/// Enum describing available notification channels. +/// +public enum NotificationChannelExt +{ + /// + /// The selected channel for the notification is email. + /// + Email +} diff --git a/src/Notifications/API/Models/NotificationOrderExt.cs b/src/Notifications/API/Models/NotificationOrderExt.cs new file mode 100644 index 00000000..90aa71c8 --- /dev/null +++ b/src/Notifications/API/Models/NotificationOrderExt.cs @@ -0,0 +1,56 @@ +#nullable enable +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Models; + +/// +/// A class representing a registered notification order. +/// +/// +/// External representaion to be used in the API. +/// +public class NotificationOrderExt : IBaseNotificationOrderExt +{ + /// > + [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 + /// + [JsonPropertyName("recipients")] + public List Recipients { get; set; } = new List(); + + /// + /// Gets or sets the emailTemplate + /// + [JsonPropertyName("emailTemplate")] + public EmailTemplateExt? EmailTemplate { get; set; } + + /// + /// Gets or sets the link of the order + /// + [JsonPropertyName("links")] + public OrderResourceLinksExt Links { get; set; } = new OrderResourceLinksExt(); +} diff --git a/src/Notifications/API/Models/NotificationOrderListExt.cs b/src/Notifications/API/Models/NotificationOrderListExt.cs new file mode 100644 index 00000000..e21362ae --- /dev/null +++ b/src/Notifications/API/Models/NotificationOrderListExt.cs @@ -0,0 +1,25 @@ +#nullable enable +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Models; + +/// +/// A class representing a list of notification order. +/// +/// +/// External representaion to be used in the API. +/// +public class NotificationOrderListExt +{ + /// + /// Gets or sets the number of orders in the list + /// + [JsonPropertyName("count")] + public int Count { get; set; } + + /// + /// Gets or sets the list of notification orders + /// + [JsonPropertyName("orders")] + public List Orders { get; set; } = new List(); +} diff --git a/src/Notifications/API/Models/NotificationOrderWithStatusExt.cs b/src/Notifications/API/Models/NotificationOrderWithStatusExt.cs new file mode 100644 index 00000000..856bd3b2 --- /dev/null +++ b/src/Notifications/API/Models/NotificationOrderWithStatusExt.cs @@ -0,0 +1,50 @@ +#nullable enable +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Models; + +/// +/// A class representing a registered notification order with status information. +/// +/// +/// External representation to be used in the API. +/// +public class NotificationOrderWithStatusExt : IBaseNotificationOrderExt +{ + /// > + [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 + /// + [JsonPropertyName("processingStatus")] + public StatusExt ProcessingStatus { get; set; } = new(); + + /// + /// Gets or sets the summary of the notifiications statuses + /// + [JsonPropertyName("notificationsStatusSummary")] + public NotificationsStatusSummaryExt? NotificationsStatusSummary { get; set; } +} diff --git a/src/Notifications/API/Models/NotificationResourceLinksExt.cs b/src/Notifications/API/Models/NotificationResourceLinksExt.cs new file mode 100644 index 00000000..72376c2d --- /dev/null +++ b/src/Notifications/API/Models/NotificationResourceLinksExt.cs @@ -0,0 +1,19 @@ +#nullable enable +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Models; + +/// +/// A class representing a set of resource links of a notification +/// +/// +/// External representaion to be used in the API. +/// +public class NotificationResourceLinksExt +{ + /// + /// Gets or sets the self link + /// + [JsonPropertyName("self")] + public string Self { get; set; } = string.Empty; +} diff --git a/src/Notifications/API/Models/NotificationStatusExt.cs b/src/Notifications/API/Models/NotificationStatusExt.cs new file mode 100644 index 00000000..bb2bb54d --- /dev/null +++ b/src/Notifications/API/Models/NotificationStatusExt.cs @@ -0,0 +1,38 @@ +#nullable enable +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Models; + +/// +/// An abstrct class representing a status overview of a notification channels +/// +public abstract class NotificationStatusExt +{ + /// + /// Gets or sets the self link of the notification status object + /// + [JsonPropertyName("links")] + public NotificationResourceLinksExt Links { get; set; } = new(); + + /// + /// Gets or sets the number of generated notifications + /// + [JsonPropertyName("generated")] + public int Generated { get; set; } + + /// + /// Gets or sets the number of succeeeded notifications + /// + [JsonPropertyName("succeeded")] + public int Succeeded { get; set; } +} + +/// +/// A class representing a status overview for email notifications +/// +/// +/// External representaion to be used in the API. +/// +public class EmailNotificationStatusExt : NotificationStatusExt +{ +} diff --git a/src/Notifications/API/Models/NotificationsStatusSummaryExt.cs b/src/Notifications/API/Models/NotificationsStatusSummaryExt.cs new file mode 100644 index 00000000..c306a9f5 --- /dev/null +++ b/src/Notifications/API/Models/NotificationsStatusSummaryExt.cs @@ -0,0 +1,19 @@ +#nullable enable +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Models; + +/// +/// A class representing a summary of status overviews of all notification channels +/// +/// +/// External representaion to be used in the API. +/// +public class NotificationsStatusSummaryExt +{ + /// + /// Gets or sets the status of the email notifications + /// + [JsonPropertyName("email")] + public EmailNotificationStatusExt? Email { get; set; } +} diff --git a/src/Notifications/API/Models/OrderIdExt.cs b/src/Notifications/API/Models/OrderIdExt.cs new file mode 100644 index 00000000..551e574e --- /dev/null +++ b/src/Notifications/API/Models/OrderIdExt.cs @@ -0,0 +1,27 @@ +#nullable enable +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 OrderIdExt +{ + /// + /// The order id + /// + [JsonPropertyName("orderId")] + public Guid OrderId { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public OrderIdExt(Guid orderId) + { + OrderId = orderId; + } +} diff --git a/src/Notifications/API/Models/OrderResourceLinksExt.cs b/src/Notifications/API/Models/OrderResourceLinksExt.cs new file mode 100644 index 00000000..843f44ec --- /dev/null +++ b/src/Notifications/API/Models/OrderResourceLinksExt.cs @@ -0,0 +1,31 @@ +#nullable enable +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Models; + +/// +/// A class representing a set of resource links of a notification order. +/// +/// +/// External representaion to be used in the API. +/// +public class OrderResourceLinksExt +{ + /// + /// Gets or sets the self link + /// + [JsonPropertyName("self")] + public string Self { get; set; } = string.Empty; + + /// + /// Gets or sets the status link + /// + [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 new file mode 100644 index 00000000..2f7a7923 --- /dev/null +++ b/src/Notifications/API/Models/RecipientExt.cs @@ -0,0 +1,19 @@ +#nullable enable +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Models; + +/// +/// Class representing a notification recipient +/// +/// +/// External representaion to be used in the API. +/// +public class RecipientExt +{ + /// + /// Gets or sets the email address of the recipient + /// + [JsonPropertyName("emailAddress")] + public string? EmailAddress { get; set; } +} diff --git a/src/Notifications/API/Models/StatusExt.cs b/src/Notifications/API/Models/StatusExt.cs new file mode 100644 index 00000000..4b7165ca --- /dev/null +++ b/src/Notifications/API/Models/StatusExt.cs @@ -0,0 +1,31 @@ +#nullable enable +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Models; + +/// +/// A class representing a status summary +/// +/// +/// External representaion to be used in the API. +/// +public class StatusExt +{ + /// + /// Gets or sets the status + /// + [JsonPropertyName("status")] + public string Status { get; set; } = string.Empty; + + /// + /// Gets or sets the description + /// + [JsonPropertyName("description")] + public string? StatusDescription { get; set; } + + /// + /// Gets or sets the date time of when the status was last updated + /// + [JsonPropertyName("lastUpdate")] + public DateTime LastUpdate { get; set; } +} diff --git a/src/Notifications/API/Validators/EmailNotificationOrderRequestValidator.cs b/src/Notifications/API/Validators/EmailNotificationOrderRequestValidator.cs new file mode 100644 index 00000000..d5165dce --- /dev/null +++ b/src/Notifications/API/Validators/EmailNotificationOrderRequestValidator.cs @@ -0,0 +1,54 @@ +#nullable enable +using System.Text.RegularExpressions; + +using Altinn.Notifications.Models; + +using FluentValidation; + +namespace Altinn.Notifications.Validators; + +/// +/// Class containing validation logic for the model +/// +public class EmailNotificationOrderRequestValidator : AbstractValidator +{ + /// + /// Initializes a new instance of the class. + /// + 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."); + + RuleFor(order => order.RequestedSendTime) + .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(); + } + + /// + /// Validated as email address based on the Altinn 2 regex + /// + /// The string to validate as an email address + /// A boolean indicating that the email is valid or not + internal static bool IsValidEmail(string? email) + { + if (string.IsNullOrEmpty(email)) + { + return false; + } + + string emailRegexPattern = @"(("[^"]+")|(([a-zA-Z0-9!#$%&'*+\-=?\^_`{|}~])+(\.([a-zA-Z0-9!#$%&'*+\-=?\^_`{|}~])+)*))@((((([a-zA-Z0-9æøåÆØÅ]([a-zA-Z0-9\-æøåÆØÅ]{0,61})[a-zA-Z0-9æøåÆØÅ]\.)|[a-zA-Z0-9æøåÆØÅ]\.){1,9})([a-zA-Z]{2,14}))|((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})))"; + + Regex regex = new(emailRegexPattern, RegexOptions.None, TimeSpan.FromSeconds(1)); + + Match match = regex.Match(email); + + return match.Success; + } +} diff --git a/src/Notifications/API/Validators/ValidationResultExtensions.cs b/src/Notifications/API/Validators/ValidationResultExtensions.cs new file mode 100644 index 00000000..18ffe907 --- /dev/null +++ b/src/Notifications/API/Validators/ValidationResultExtensions.cs @@ -0,0 +1,23 @@ +#nullable enable +using FluentValidation.Results; + +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Altinn.Notifications.Validators; + +/// +/// Extension class for +/// +public static class ValidationResultExtensions +{ + /// + /// Adds the validation result to the model state + /// + public static void AddToModelState(this ValidationResult result, ModelStateDictionary modelState) + { + foreach (var error in result.Errors) + { + modelState.AddModelError(error.PropertyName, error.ErrorMessage); + } + } +} diff --git a/src/Notifications/Core/Configuration/NotificationOrderConfig.cs b/src/Notifications/Core/Configuration/NotificationOrderConfig.cs new file mode 100644 index 00000000..571b9cf1 --- /dev/null +++ b/src/Notifications/Core/Configuration/NotificationOrderConfig.cs @@ -0,0 +1,13 @@ +#nullable enable +namespace Altinn.Notifications.Core.Configuration; + +/// +/// Configuration class for notification orders +/// +public class NotificationOrderConfig +{ + /// + /// Default from address for email notifications + /// + public string DefaultEmailFromAddress { get; set; } = string.Empty; +} diff --git a/src/Notifications/Core/Enums/AddressType.cs b/src/Notifications/Core/Enums/AddressType.cs new file mode 100644 index 00000000..caf5ca9a --- /dev/null +++ b/src/Notifications/Core/Enums/AddressType.cs @@ -0,0 +1,12 @@ +#nullable enable +namespace Altinn.Notifications.Core.Enums; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +/// +/// Enum describing available address types +/// +public enum AddressType +{ + Email +} +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member diff --git a/src/Notifications/Core/Enums/EmailContentType.cs b/src/Notifications/Core/Enums/EmailContentType.cs new file mode 100644 index 00000000..991ae849 --- /dev/null +++ b/src/Notifications/Core/Enums/EmailContentType.cs @@ -0,0 +1,18 @@ +#nullable enable +namespace Altinn.Notifications.Core.Enums; + +/// +/// Enum describing available email content types +/// +public enum EmailContentType +{ + /// + /// The email format is plain text. + /// + Plain, + + /// + /// The email contains HTML elements + /// + Html +} diff --git a/src/Notifications/Core/Enums/EmailNotificationResultType.cs b/src/Notifications/Core/Enums/EmailNotificationResultType.cs new file mode 100644 index 00000000..28e0562d --- /dev/null +++ b/src/Notifications/Core/Enums/EmailNotificationResultType.cs @@ -0,0 +1,43 @@ +#nullable enable +namespace Altinn.Notifications.Core.Enums; + +/// +/// Enum describing email notification result types +/// +public enum EmailNotificationResultType +{ + /// + /// Default result for new notifications + /// + New, + + /// + /// Email notification being sent + /// + Sending, + + /// + /// Email notification sent + /// + Succeeded, + + /// + /// Email delivered to recipient + /// + Delivered, + + /// + /// Failed, unknown reason + /// + Failed, + + /// + /// Recipient to address was not identified + /// + Failed_RecipientNotIdentified, + + /// + /// Invalid format for email address + /// + Failed_InvalidEmailFormat +} diff --git a/src/Notifications/Core/Enums/IResultType.cs b/src/Notifications/Core/Enums/IResultType.cs new file mode 100644 index 00000000..d8dea4d1 --- /dev/null +++ b/src/Notifications/Core/Enums/IResultType.cs @@ -0,0 +1,9 @@ +#nullable enable +namespace Altinn.Notifications.Core.Enums; + +/// +/// Base class for send result of a notification +/// +public interface IResultType +{ +} diff --git a/src/Notifications/Core/Enums/NotificationChannel.cs b/src/Notifications/Core/Enums/NotificationChannel.cs new file mode 100644 index 00000000..4e23d060 --- /dev/null +++ b/src/Notifications/Core/Enums/NotificationChannel.cs @@ -0,0 +1,13 @@ +#nullable enable +namespace Altinn.Notifications.Core.Enums; + +/// +/// Enum describing available notification channels. +/// +public enum NotificationChannel +{ + /// + /// The selected channel for the notification is email. + /// + Email +} diff --git a/src/Notifications/Core/Enums/NotificationTemplateType.cs b/src/Notifications/Core/Enums/NotificationTemplateType.cs new file mode 100644 index 00000000..a7e02909 --- /dev/null +++ b/src/Notifications/Core/Enums/NotificationTemplateType.cs @@ -0,0 +1,12 @@ +#nullable enable +namespace Altinn.Notifications.Core.Enums; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +/// +/// Enum describing available notification template types +/// +public enum NotificationTemplateType +{ + Email +} +#pragma warning restore 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 new file mode 100644 index 00000000..20c22fa9 --- /dev/null +++ b/src/Notifications/Core/Enums/OrderProcessingStatus.cs @@ -0,0 +1,14 @@ +#nullable enable +namespace Altinn.Notifications.Core.Enums; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +/// +/// Enum describing the various processing states of a notification order +/// +public enum OrderProcessingStatus +{ + Registered, + Processing, + Completed +} +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member diff --git a/src/Notifications/Core/Models/Address/EmailAddressPoint.cs b/src/Notifications/Core/Models/Address/EmailAddressPoint.cs new file mode 100644 index 00000000..aa2b3031 --- /dev/null +++ b/src/Notifications/Core/Models/Address/EmailAddressPoint.cs @@ -0,0 +1,35 @@ +#nullable enable +using Altinn.Notifications.Core.Enums; + +namespace Altinn.Notifications.Core.Models.Address; + +/// +/// A class represeting an address point +/// +public class EmailAddressPoint : IAddressPoint +{ + /// + public AddressType AddressType { get; internal set; } + + /// + /// Gets the email address + /// + public string EmailAddress { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + public EmailAddressPoint(string emailAddress) + { + AddressType = AddressType.Email; + EmailAddress = emailAddress; + } + + /// + /// Initializes a new instance of the class. + /// + internal EmailAddressPoint() + { + EmailAddress = string.Empty; + } +} diff --git a/src/Notifications/Core/Models/Address/IAddressPoint.cs b/src/Notifications/Core/Models/Address/IAddressPoint.cs new file mode 100644 index 00000000..e15ccc17 --- /dev/null +++ b/src/Notifications/Core/Models/Address/IAddressPoint.cs @@ -0,0 +1,19 @@ +#nullable enable +using System.Text.Json.Serialization; + +using Altinn.Notifications.Core.Enums; + +namespace Altinn.Notifications.Core.Models.Address; + +/// +/// Interface describing an address point +/// +[JsonDerivedType(typeof(EmailAddressPoint), "email")] +[JsonPolymorphic(TypeDiscriminatorPropertyName = "$")] +public interface IAddressPoint +{ + /// + /// Gets or sets the address type for the address point + /// + public AddressType AddressType { get; } +} diff --git a/src/Notifications/Core/Models/Creator.cs b/src/Notifications/Core/Models/Creator.cs new file mode 100644 index 00000000..ef0e334a --- /dev/null +++ b/src/Notifications/Core/Models/Creator.cs @@ -0,0 +1,21 @@ +#nullable enable +namespace Altinn.Notifications.Core.Models; + +/// +/// A class representing a notification creator +/// +public class Creator +{ + /// + /// Gets the short name of the creator + /// + public string ShortName { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + public Creator(string shortName) + { + ShortName = shortName; + } +} diff --git a/src/Notifications/Core/Models/Email.cs b/src/Notifications/Core/Models/Email.cs new file mode 100644 index 00000000..00a3ecce --- /dev/null +++ b/src/Notifications/Core/Models/Email.cs @@ -0,0 +1,71 @@ +#nullable enable +using System.Text.Json; +using System.Text.Json.Serialization; + +using Altinn.Notifications.Core.Enums; + +namespace Altinn.Notifications.Core.Models; + +/// +/// Class representing an email +/// +public class Email +{ + /// + /// Gets or sets the id of the email. + /// + public Guid NotificationId { get; set; } + + /// + /// Gets or sets the subject of the email. + /// + public string Subject { get; set; } + + /// + /// Gets or sets the body of the email. + /// + public string Body { get; set; } + + /// + /// Gets or sets the to fromAdress of the email. + /// + public string FromAddress { get; set; } + + /// + /// Gets or sets the to adress of the email. + /// + public string ToAddress { get; set; } + + /// + /// Gets or sets the content type of the email. + /// + public EmailContentType ContentType { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public Email(Guid notificationId, string subject, string body, string fromAddress, string toAddress, EmailContentType contentType) + { + NotificationId = notificationId; + Subject = subject; + Body = body; + FromAddress = fromAddress; + ToAddress = toAddress; + ContentType = contentType; + } + + /// + /// Json serializes the + /// + public string Serialize() + { + return JsonSerializer.Serialize( + this, + new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = { new JsonStringEnumConverter() } + }); + } +} diff --git a/src/Notifications/Core/Models/Notification/EmailNotification.cs b/src/Notifications/Core/Models/Notification/EmailNotification.cs new file mode 100644 index 00000000..3348ab48 --- /dev/null +++ b/src/Notifications/Core/Models/Notification/EmailNotification.cs @@ -0,0 +1,57 @@ +#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 new file mode 100644 index 00000000..28d97540 --- /dev/null +++ b/src/Notifications/Core/Models/Notification/EmailNotificationSummary.cs @@ -0,0 +1,32 @@ +#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 new file mode 100644 index 00000000..a8c7e851 --- /dev/null +++ b/src/Notifications/Core/Models/Notification/EmailNotificationWithResult.cs @@ -0,0 +1,46 @@ +#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 new file mode 100644 index 00000000..4bee1b10 --- /dev/null +++ b/src/Notifications/Core/Models/Notification/INotification.cs @@ -0,0 +1,38 @@ +#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 new file mode 100644 index 00000000..911453b2 --- /dev/null +++ b/src/Notifications/Core/Models/Notification/INotificationSummary.cs @@ -0,0 +1,34 @@ +#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 new file mode 100644 index 00000000..aa95da12 --- /dev/null +++ b/src/Notifications/Core/Models/Notification/INotificationWithResult.cs @@ -0,0 +1,32 @@ +#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 new file mode 100644 index 00000000..1f85bb3a --- /dev/null +++ b/src/Notifications/Core/Models/Notification/NotificationResult.cs @@ -0,0 +1,41 @@ +#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 new file mode 100644 index 00000000..2031dcaa --- /dev/null +++ b/src/Notifications/Core/Models/Notification/SendOperationResult.cs @@ -0,0 +1,85 @@ +#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 new file mode 100644 index 00000000..b3b627d5 --- /dev/null +++ b/src/Notifications/Core/Models/NotificationTemplate/EmailTemplate.cs @@ -0,0 +1,52 @@ +#nullable enable +using Altinn.Notifications.Core.Enums; + +namespace Altinn.Notifications.Core.Models.NotificationTemplate; + +/// +/// Template for an email notification +/// +public class EmailTemplate : INotificationTemplate +{ + /// + public NotificationTemplateType Type { get; internal set; } + + /// + /// Gets the from adress of emails created by the template + /// + public string FromAddress { get; internal set; } = string.Empty; + + /// + /// Gets the subject of emails created by the template + /// + public string Subject { get; internal set; } = string.Empty; + + /// + /// Gets the body of emails created by the template + /// + public string Body { get; internal set; } = string.Empty; + + /// + /// Gets the content type of emails created by the template + /// + public EmailContentType ContentType { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + public EmailTemplate(string? fromAddress, string subject, string body, EmailContentType contentType) + { + FromAddress = fromAddress ?? string.Empty; + Subject = subject; + Body = body; + ContentType = contentType; + Type = NotificationTemplateType.Email; + } + + /// + /// Initializes a new instance of the class. + /// + internal EmailTemplate() + { + } +} diff --git a/src/Notifications/Core/Models/NotificationTemplate/INotificationTemplate.cs b/src/Notifications/Core/Models/NotificationTemplate/INotificationTemplate.cs new file mode 100644 index 00000000..106efd09 --- /dev/null +++ b/src/Notifications/Core/Models/NotificationTemplate/INotificationTemplate.cs @@ -0,0 +1,19 @@ +#nullable enable +using System.Text.Json.Serialization; + +using Altinn.Notifications.Core.Enums; + +namespace Altinn.Notifications.Core.Models.NotificationTemplate; + +/// +/// Base class for a notification template +/// +[JsonDerivedType(typeof(EmailTemplate), "email")] +[JsonPolymorphic(TypeDiscriminatorPropertyName = "$")] +public interface INotificationTemplate +{ + /// + /// Gets the type for the template + /// + public NotificationTemplateType Type { get; } +} diff --git a/src/Notifications/Core/Models/Orders/IBaseNotificationOrder.cs b/src/Notifications/Core/Models/Orders/IBaseNotificationOrder.cs new file mode 100644 index 00000000..64d891c6 --- /dev/null +++ b/src/Notifications/Core/Models/Orders/IBaseNotificationOrder.cs @@ -0,0 +1,40 @@ +#nullable enable +using Altinn.Notifications.Core.Enums; + +namespace Altinn.Notifications.Core.Models.Orders; + +/// +/// Class representing the base properties of a notification order +/// +public interface IBaseNotificationOrder +{ + /// + /// Gets the id of the notification order + /// + public Guid Id { get; } + + /// + /// Gets the senders reference of a notification + /// + public string? SendersReference { get; } + + /// + /// Gets the requested send time for the notification(s) + /// + public DateTime RequestedSendTime { get; } + + /// + /// Gets the preferred notification channel + /// + public NotificationChannel NotificationChannel { get; } + + /// + /// Gets the creator of the notification + /// + public Creator Creator { get; } + + /// + /// Gets the date and time for when the notification order was created + /// + public DateTime Created { get; } +} diff --git a/src/Notifications/Core/Models/Orders/NotificationOrder.cs b/src/Notifications/Core/Models/Orders/NotificationOrder.cs new file mode 100644 index 00000000..96dfee79 --- /dev/null +++ b/src/Notifications/Core/Models/Orders/NotificationOrder.cs @@ -0,0 +1,122 @@ +#nullable enable +using System.Text.Json; +using System.Text.Json.Serialization; + +using Altinn.Notifications.Core.Enums; +using Altinn.Notifications.Core.Models.NotificationTemplate; + +namespace Altinn.Notifications.Core.Models.Orders; + +/// +/// Class representing a notification order +/// +public class NotificationOrder : IBaseNotificationOrder +{ + /// > + public Guid Id { get; internal set; } = Guid.Empty; + + /// > + public string? SendersReference { get; internal set; } + + /// > + public DateTime RequestedSendTime { get; internal set; } + + /// > + public NotificationChannel NotificationChannel { get; internal set; } + + /// > + public Creator Creator { get; internal set; } + + /// > + public DateTime Created { get; internal set; } + + /// + /// Gets the templates to create notifications based of + /// + public List Templates { get; internal set; } = new List(); + + /// + /// Gets a list of recipients + /// + public List Recipients { get; internal set; } = new List(); + + /// + /// 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) + { + Id = id; + SendersReference = sendersReference; + Templates = templates; + RequestedSendTime = requestedSendTime; + NotificationChannel = notificationChannel; + Creator = creator; + Created = created; + Recipients = recipients; + } + + /// + /// Initializes a new instance of the class. + /// + internal NotificationOrder() + { + Creator = new Creator(string.Empty); + } + + /// + /// 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 NotificationOrder? 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 NotificationOrder value) + { + NotificationOrder? parsedOutput; + value = new NotificationOrder(); + + if (string.IsNullOrEmpty(input)) + { + return false; + } + + try + { + parsedOutput = Deserialize(input!); + + value = parsedOutput!; + return value.Id != Guid.Empty; + } + catch + { + // try parse, we simply return false if fails + } + + return false; + } +} diff --git a/src/Notifications/Core/Models/Orders/NotificationOrderRequest.cs b/src/Notifications/Core/Models/Orders/NotificationOrderRequest.cs new file mode 100644 index 00000000..9f794c3b --- /dev/null +++ b/src/Notifications/Core/Models/Orders/NotificationOrderRequest.cs @@ -0,0 +1,64 @@ +#nullable enable +using Altinn.Notifications.Core.Enums; +using Altinn.Notifications.Core.Models.NotificationTemplate; + +namespace Altinn.Notifications.Core.Models.Orders; + +/// +/// Class representing a notification order request +/// +public class NotificationOrderRequest +{ + /// + /// Gets the senders reference of a notification + /// + public string? SendersReference { get; internal set; } + + /// + /// Gets the templates to create notifications based of + /// + public List Templates { get; internal set; } + + /// + /// Gets the requested send time for the notification(s) + /// + public DateTime RequestedSendTime { get; internal set; } + + /// + /// Gets the preferred notification channel + /// + public NotificationChannel NotificationChannel { get; internal set; } + + /// + /// Gets a list of recipients + /// + public List Recipients { get; internal set; } + + /// + /// Gets the creator of the notification request + /// + public Creator Creator { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + public NotificationOrderRequest(string? sendersReference, string creatorShortName, List templates, DateTime requestedSendTime, NotificationChannel notificationChannel, List recipients) + { + SendersReference = sendersReference; + Creator = new(creatorShortName); + Templates = templates; + RequestedSendTime = requestedSendTime; + NotificationChannel = notificationChannel; + Recipients = recipients; + } + + /// + /// Initializes a new instance of the class. + /// + internal NotificationOrderRequest() + { + Creator = new Creator(string.Empty); + Templates = new List(); + Recipients = new List(); + } +} diff --git a/src/Notifications/Core/Models/Orders/NotificationOrderWithStatus.cs b/src/Notifications/Core/Models/Orders/NotificationOrderWithStatus.cs new file mode 100644 index 00000000..5c79f450 --- /dev/null +++ b/src/Notifications/Core/Models/Orders/NotificationOrderWithStatus.cs @@ -0,0 +1,134 @@ +#nullable enable +using System.Text.Json.Serialization; + +using Altinn.Notifications.Core.Enums; + +namespace Altinn.Notifications.Core.Models.Orders; + +/// +/// A class representing a registered notification order with status information. +/// +public class NotificationOrderWithStatus : IBaseNotificationOrder +{ + /// > + public Guid Id { get; internal set; } + + /// > + public string? SendersReference { get; internal set; } + + /// > + public DateTime RequestedSendTime { get; internal set; } + + /// > + public Creator Creator { get; internal set; } = new(string.Empty); + + /// > + public DateTime Created { get; internal set; } + + /// > + public NotificationChannel NotificationChannel { get; internal set; } + + /// + /// Gets the processing status of the notication order + /// + public ProcessingStatus ProcessingStatus { get; internal set; } = new(); + + /// + /// Gets the summary of the notifiications statuses + /// + public Dictionary NotificationStatuses { get; set; } = new(); + + /// + /// Initializes a new instance of the class. + /// + public NotificationOrderWithStatus(Guid id, string? sendersReference, DateTime requestedSendTime, Creator creator, DateTime created, NotificationChannel notificationChannel, ProcessingStatus processingStatus) + { + Id = id; + SendersReference = sendersReference; + RequestedSendTime = requestedSendTime; + Creator = creator; + Created = created; + NotificationChannel = notificationChannel; + ProcessingStatus = processingStatus; + } + + /// + /// Initializes a new instance of the class. + /// + internal NotificationOrderWithStatus() + { + } + + /// + /// Adds an entry to the notification statuses for the provided type + /// + public void SetNotificationStatuses(NotificationTemplateType type, int generated, int succeeded) + { + NotificationStatuses.Add(type, new NotificationStatus() { Generated = generated, Succeeded = succeeded }); + } +} + +/// +/// A class representing a summary of status overviews of all notification channels +/// +/// +/// External representaion to be used in the API. +/// +public class ProcessingStatus +{ + /// + /// Gets the status + /// + [JsonPropertyName("status")] + public OrderProcessingStatus Status { get; internal set; } + + /// + /// Gets the description + /// + [JsonPropertyName("description")] + public string? StatusDescription { get; internal set; } + + /// + /// Gets the date time of when the status was last updated + /// + [JsonPropertyName("lastUpdate")] + public DateTime LastUpdate { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + public ProcessingStatus(OrderProcessingStatus status, DateTime lastUpdate, string? statusDescription = null) + { + Status = status; + StatusDescription = statusDescription; + LastUpdate = lastUpdate; + } + + /// + /// Initializes a new instance of the class. + /// + internal ProcessingStatus() + { + } +} + +/// +/// A class representing a summary of status overviews of all notification channels +/// +/// +/// External representaion to be used in the API. +/// +public class NotificationStatus +{ + /// + /// Gets the number of generated notifications + /// + [JsonPropertyName("generated")] + public int Generated { get; internal set; } + + /// + /// Gets the number of succeeeded notifications + /// + [JsonPropertyName("succeeded")] + public int Succeeded { get; internal set; } +} diff --git a/src/Notifications/Core/Models/Recipient.cs b/src/Notifications/Core/Models/Recipient.cs new file mode 100644 index 00000000..c0517c41 --- /dev/null +++ b/src/Notifications/Core/Models/Recipient.cs @@ -0,0 +1,44 @@ +#nullable enable +using Altinn.Notifications.Core.Models.Address; + +namespace Altinn.Notifications.Core.Models; + +/// +/// Class representing a notification recipient +/// +public class Recipient +{ + /// + /// Gets the recipient id + /// + public string RecipientId { get; set; } = string.Empty; + + /// + /// Gets a list of address points for the recipient + /// + public List AddressInfo { get; set; } = new List(); + + /// + /// Initializes a new instance of the class. + /// + public Recipient(string recipientId, List addressInfo) + { + RecipientId = recipientId; + AddressInfo = addressInfo; + } + + /// + /// Initializes a new instance of the class. + /// + public Recipient(List addressInfo) + { + AddressInfo = addressInfo; + } + + /// + /// Initializes a new instance of the class. + /// + public Recipient() + { + } +} diff --git a/src/Notifications/Core/Models/Recipients/EmailRecipient.cs b/src/Notifications/Core/Models/Recipients/EmailRecipient.cs new file mode 100644 index 00000000..8d9e9216 --- /dev/null +++ b/src/Notifications/Core/Models/Recipients/EmailRecipient.cs @@ -0,0 +1,18 @@ +#nullable enable +namespace Altinn.Notifications.Core.Models.Recipients; + +/// +/// Class representing an email recipient +/// +public class EmailRecipient +{ + /// + /// Gets or sets the recipient id + /// + public string? RecipientId { get; set; } = null; + + /// + /// Gets or sets the toaddress + /// + public string ToAddress { get; set; } = string.Empty; +} diff --git a/src/Notifications/Core/Models/ServiceError.cs b/src/Notifications/Core/Models/ServiceError.cs new file mode 100644 index 00000000..12658322 --- /dev/null +++ b/src/Notifications/Core/Models/ServiceError.cs @@ -0,0 +1,36 @@ +#nullable enable +namespace Altinn.Notifications.Core.Models; + +/// +/// A class representing a service error object used to transfere error information from service to controller. +/// +public class ServiceError +{ + /// + /// The error code + /// + /// An error code translates directly into an HTTP status code + public int ErrorCode { get; private set; } + + /// + /// The error message + /// + public string? ErrorMessage { get; private set; } + + /// + /// Create a new instance of a service error + /// + public ServiceError(int errorCode, string errorMessage) + { + ErrorCode = errorCode; + ErrorMessage = errorMessage; + } + + /// + /// Create a new instance of a service error + /// + public ServiceError(int errorCode) + { + ErrorCode = errorCode; + } +} diff --git a/src/Notifications/Core/Repository/Interfaces/IOrderRepository.cs b/src/Notifications/Core/Repository/Interfaces/IOrderRepository.cs new file mode 100644 index 00000000..e6050b48 --- /dev/null +++ b/src/Notifications/Core/Repository/Interfaces/IOrderRepository.cs @@ -0,0 +1,53 @@ +#nullable enable +using Altinn.Notifications.Core.Enums; +using Altinn.Notifications.Core.Models.Orders; + +namespace Altinn.Notifications.Core.Repository.Interfaces; + +/// +/// Interface describing all repository actions for notification orders +/// +public interface IOrderRepository +{ + /// + /// Creates a new notification order in the database + /// + /// The order to save + /// The saved notification order + public Task Create(NotificationOrder order); + + /// + /// Gets a list of notification orders where requestedSendTime has passed + /// + /// A list of notification orders + public Task> GetPastDueOrdersAndSetProcessingState(); + + /// + /// Sets processing status of an order + /// + public Task SetProcessingStatus(Guid orderId, OrderProcessingStatus status); + + /// + /// Gets an order based on the provided id within the provided creator scope + /// + /// The order id + /// The short name of the order creator + /// A notification order if it exists + public Task GetOrderById(Guid id, string creator); + + /// + /// Gets an order with process and notification status based on the provided id within the provided creator scope + /// + /// The order id + /// The short name of the order creator + /// A notification order if it exists + public Task GetOrderWithStatusById(Guid id, string creator); + + /// + /// Gets an order based on the provided senders reference within the provided creator scope + /// + /// The senders reference + /// The short name of the order creator + /// A list of notification orders + public Task> GetOrdersBySendersReference(string sendersReference, string creator); +} diff --git a/src/Notifications/Core/Services/DateTimeService.cs b/src/Notifications/Core/Services/DateTimeService.cs new file mode 100644 index 00000000..a48321cd --- /dev/null +++ b/src/Notifications/Core/Services/DateTimeService.cs @@ -0,0 +1,19 @@ +#nullable enable +using System.Diagnostics.CodeAnalysis; + +using Altinn.Notifications.Core.Services.Interfaces; + +namespace Altinn.Notifications.Core.Services; + +/// +/// Implemntation of a dateTime service +/// +[ExcludeFromCodeCoverage] +public class DateTimeService : IDateTimeService +{ + /// + public DateTime UtcNow() + { + return DateTime.UtcNow; + } +} diff --git a/src/Notifications/Core/Services/EmailNotificationOrderService.cs b/src/Notifications/Core/Services/EmailNotificationOrderService.cs new file mode 100644 index 00000000..0535e4a0 --- /dev/null +++ b/src/Notifications/Core/Services/EmailNotificationOrderService.cs @@ -0,0 +1,66 @@ +#nullable enable +using Altinn.Notifications.Core.Configuration; +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.Services.Interfaces; + +using Microsoft.Extensions.Options; + +namespace Altinn.Notifications.Core.Services; + +/// +/// Implementation of the . +/// +public class EmailNotificationOrderService : IEmailNotificationOrderService +{ + private readonly IOrderRepository _repository; + private readonly IGuidService _guid; + private readonly IDateTimeService _dateTime; + private readonly string _defaultFromAddress; + + /// + /// Initializes a new instance of the class. + /// + public EmailNotificationOrderService(IOrderRepository repository, IGuidService guid, IDateTimeService dateTime, IOptions config) + { + _repository = repository; + _guid = guid; + _dateTime = dateTime; + _defaultFromAddress = config.Value.DefaultEmailFromAddress; + } + + /// + public async Task<(NotificationOrder? Order, ServiceError? Error)> RegisterEmailNotificationOrder(NotificationOrderRequest orderRequest) + { + Guid orderId = _guid.NewGuid(); + DateTime created = _dateTime.UtcNow(); + + var templates = SetFromAddressIfNotDefined(orderRequest.Templates); + + var order = new NotificationOrder( + orderId, + orderRequest.SendersReference, + templates, + orderRequest.RequestedSendTime, + orderRequest.NotificationChannel, + orderRequest.Creator, + created, + orderRequest.Recipients); + + NotificationOrder savedOrder = await _repository.Create(order); + + return (savedOrder, null); + } + + private List SetFromAddressIfNotDefined(List templates) + { + foreach (var template in templates.OfType().Where(template => string.IsNullOrEmpty(template.FromAddress))) + { + template.FromAddress = _defaultFromAddress; + } + + return templates; + } +} diff --git a/src/Notifications/Core/Services/GuidService.cs b/src/Notifications/Core/Services/GuidService.cs new file mode 100644 index 00000000..d6614803 --- /dev/null +++ b/src/Notifications/Core/Services/GuidService.cs @@ -0,0 +1,19 @@ +#nullable enable +using System.Diagnostics.CodeAnalysis; + +using Altinn.Notifications.Core.Services.Interfaces; + +namespace Altinn.Notifications.Core.Services; + +/// +/// Implementation of the GuidServiceS +/// +[ExcludeFromCodeCoverage] +public class GuidService : IGuidService +{ + /// + public Guid NewGuid() + { + return Guid.NewGuid(); + } +} diff --git a/src/Notifications/Core/Services/Interfaces/IDateTimeService.cs b/src/Notifications/Core/Services/Interfaces/IDateTimeService.cs new file mode 100644 index 00000000..8ff6b2b1 --- /dev/null +++ b/src/Notifications/Core/Services/Interfaces/IDateTimeService.cs @@ -0,0 +1,14 @@ +#nullable enable +namespace Altinn.Notifications.Core.Services.Interfaces; + +/// +/// Interface describing a dateTime service +/// +public interface IDateTimeService +{ + /// + /// Provides DateTime UtcNow + /// + /// + public DateTime UtcNow(); +} diff --git a/src/Notifications/Core/Services/Interfaces/IEmailNotificationOrderService.cs b/src/Notifications/Core/Services/Interfaces/IEmailNotificationOrderService.cs new file mode 100644 index 00000000..01d54a36 --- /dev/null +++ b/src/Notifications/Core/Services/Interfaces/IEmailNotificationOrderService.cs @@ -0,0 +1,18 @@ +#nullable enable +using Altinn.Notifications.Core.Models; +using Altinn.Notifications.Core.Models.Orders; + +namespace Altinn.Notifications.Core.Services.Interfaces; + +/// +/// Interface for the email notification order service +/// +public interface IEmailNotificationOrderService +{ + /// + /// Registers a new order + /// + /// The email notification order request + /// The registered notification order + public Task<(NotificationOrder? Order, ServiceError? Error)> RegisterEmailNotificationOrder(NotificationOrderRequest orderRequest); +} diff --git a/src/Notifications/Core/Services/Interfaces/IGuidService.cs b/src/Notifications/Core/Services/Interfaces/IGuidService.cs new file mode 100644 index 00000000..16b81efc --- /dev/null +++ b/src/Notifications/Core/Services/Interfaces/IGuidService.cs @@ -0,0 +1,13 @@ +#nullable enable +namespace Altinn.Notifications.Core.Services.Interfaces; + +/// +/// Interface describing a guid service +/// +public interface IGuidService +{ + /// + /// Generates a new Guid + /// + public Guid NewGuid(); +} diff --git a/src/Notifications/LocalTestNotifications/LocalOrderRepository.cs b/src/Notifications/LocalTestNotifications/LocalOrderRepository.cs new file mode 100644 index 00000000..4b3cab55 --- /dev/null +++ b/src/Notifications/LocalTestNotifications/LocalOrderRepository.cs @@ -0,0 +1,79 @@ +#nullable enable +using System.Text.Json; + +using Altinn.Notifications.Core.Enums; +using Altinn.Notifications.Core.Models.Orders; +using Altinn.Notifications.Core.Repository.Interfaces; + +using LocalTest.Configuration; + +using Microsoft.Extensions.Options; + +namespace LocalTest.Notifications.Persistence.Repository +{ + public class LocalOrderRepository : IOrderRepository + { + private readonly LocalPlatformSettings _localPlatformSettings; + private readonly JsonSerializerOptions _serializerOptions; + + public LocalOrderRepository( + IOptions localPlatformSettings) + { + _localPlatformSettings = localPlatformSettings.Value; + Directory.CreateDirectory(GetNotificationsDbPath()); + + _serializerOptions = new JsonSerializerOptions + { + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + } + + public Task Create(NotificationOrder order) + { + string path = GetOrderPath(order.Id); + + string serializedOrder = JsonSerializer.Serialize(order, _serializerOptions); + FileInfo file = new FileInfo(path); + file.Directory?.Create(); + File.WriteAllText(file.FullName, serializedOrder); + + return Task.FromResult(order); + } + + public Task GetOrderById(Guid id, string creator) + { + throw new NotImplementedException(); + } + + public Task> GetOrdersBySendersReference(string sendersReference, string creator) + { + throw new NotImplementedException(); + } + + public Task GetOrderWithStatusById(Guid id, string creator) + { + throw new NotImplementedException(); + } + + public Task> GetPastDueOrdersAndSetProcessingState() + { + throw new NotImplementedException(); + } + + public Task SetProcessingStatus(Guid orderId, OrderProcessingStatus status) + { + throw new NotImplementedException(); + } + + private string GetOrderPath(Guid orderId) + { + return Path.Combine(GetNotificationsDbPath(), "orders", orderId + ".json"); + } + + private string GetNotificationsDbPath() + { + return _localPlatformSettings.LocalTestingStorageBasePath + _localPlatformSettings.NotificationsStorageFolder; + } + } +} diff --git a/src/Notifications/LocalTestNotifications/NotificationsServiceExtentions.cs b/src/Notifications/LocalTestNotifications/NotificationsServiceExtentions.cs new file mode 100644 index 00000000..f9f101e5 --- /dev/null +++ b/src/Notifications/LocalTestNotifications/NotificationsServiceExtentions.cs @@ -0,0 +1,30 @@ +using Altinn.Notifications.Core.Configuration; +using Altinn.Notifications.Core.Repository.Interfaces; +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) + { + // Notifications services + ValidatorOptions.Global.LanguageManager.Enabled = false; + ResourceLinkExtensions.Initialize(baseUrl); + + services.Configure((c) => c.DefaultEmailFromAddress = "localtest@altinn.no"); + + services + .AddSingleton, EmailNotificationOrderRequestValidator>() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(); + } +} \ No newline at end of file diff --git a/src/Notifications/README.md b/src/Notifications/README.md new file mode 100644 index 00000000..edbb7d05 --- /dev/null +++ b/src/Notifications/README.md @@ -0,0 +1,10 @@ +## Mock of Altinn Notifications for localtest + +Repository: https://github.com/Altinn/altinn-notifications + +Things to be aware of when copying code: + +- Persistence: code should not be copied directly, rather create a new implementation of the required repository class/method that writes data to disk. - Controllers: code should be copied, but changes might be required related to authorization (e.g., platform access token is not included in local requests) +- Core: code can be copied directly without changes + +Update Startup.cs with required services. \ No newline at end of file diff --git a/src/Startup.cs b/src/Startup.cs index 532a7f28..ef45057f 100644 --- a/src/Startup.cs +++ b/src/Startup.cs @@ -28,6 +28,7 @@ using LocalTest.Clients.CdnAltinnOrgs; using LocalTest.Configuration; using LocalTest.Helpers; +using LocalTest.Notifications.LocalTestNotifications; using LocalTest.Services.Authentication.Implementation; using LocalTest.Services.Authentication.Interface; using LocalTest.Services.Authorization.Implementation; @@ -43,14 +44,7 @@ using LocalTest.Services.TestData; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.StaticFiles.Infrastructure; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; @@ -127,6 +121,11 @@ public void ConfigureServices(IServiceCollection services) services.AddTransient(); services.AddTransient(); + // Notifications services + + GeneralSettings generalSettings = Configuration.GetSection("GeneralSettings").Get(); + services.AddNotificationServices(generalSettings.BaseUrl); + // Storage services services.AddSingleton(); services.AddTransient();