Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new notification features #101

Merged
merged 9 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/LocalTest.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
Expand All @@ -17,10 +17,10 @@
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0" />
<PackageReference Include="FluentValidation" Version="11.8.0" />
<PackageReference Include="JWTCookieAuthentication" Version="2.4.2" />
<PackageReference Include="libphonenumber-csharp" Version="8.13.34" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="libphonenumber-csharp" Version="8.13.32" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.Filters;
#endif

namespace Altinn.Notifications.Controllers;

/// <summary>
Expand Down Expand Up @@ -84,15 +83,10 @@ public async Task<ActionResult<OrderIdExt>> Post(EmailNotificationOrderRequestEx
}
#endif


var orderRequest = emailNotificationOrderRequest.MapToOrderRequest(creator);
Result<NotificationOrder, ServiceError> 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());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,9 @@ public async Task<ActionResult<OrderIdExt>> Post(SmsNotificationOrderRequestExt
}
#endif

NotificationOrderRequest orderRequest = smsNotificationOrderRequest.MapToOrderRequest(creator);
Result<NotificationOrder, ServiceError> 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());
}
}
15 changes: 15 additions & 0 deletions src/Notifications/API/Extensions/HttpContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Altinn.Notifications.Extensions;

/// <summary>
/// Extensions for HTTP Context
/// </summary>
public static class HttpContextExtensions
{
/// <summary>
/// Get the org string from the context items or null if it is not defined
/// </summary>
public static string? GetOrg(this HttpContext context)

Check warning on line 11 in src/Notifications/API/Extensions/HttpContextExtensions.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
return context.Items["Org"] as string;
}
}
27 changes: 20 additions & 7 deletions src/Notifications/API/Extensions/ResourceLinkExtensions.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,7 +8,7 @@
/// </summary>
public static class ResourceLinkExtensions
{
private static string? _baseUri;

Check warning on line 11 in src/Notifications/API/Extensions/ResourceLinkExtensions.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

/// <summary>
/// Initializes the ResourceLinkExtensions with the base URI from settings.
Expand Down Expand Up @@ -39,7 +38,6 @@
{
Self = self,
Status = self + "/status",
Notifications = self + "/notifications"
};
}

Expand All @@ -56,26 +54,41 @@

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"
};
}
}

/// <summary>
/// Gets the self link for the provided notification order
/// </summary>
/// <exception cref="InvalidOperationException">Exception if class has not been initialized in Program.cs</exception>
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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Altinn.Notifications.Core.Models.Orders;
using Altinn.Notifications.Models;

namespace Altinn.Notifications.Mappers;

/// <summary>
/// Mapper class
/// </summary>
public static class NotificationOrderRequestResponseMapper
{
/// <summary>
/// Maps a <see cref="NotificationOrderRequestResponse"/> to a <see cref="NotificationOrderRequestResponseExt"/>
/// </summary>
public static NotificationOrderRequestResponseExt MapToExternal(this NotificationOrderRequestResponse requestResponse)
{
NotificationOrderRequestResponseExt ext = new()
{
OrderId = requestResponse.OrderId
};

if (requestResponse.RecipientLookup != null)
{
ext.RecipientLookup = new RecipientLookupResultExt
{
Status = Enum.Parse<RecipientLookupStatusExt>(requestResponse.RecipientLookup.Status.ToString(), true),
IsReserved = requestResponse.RecipientLookup?.IsReserved,
MissingContact = requestResponse.RecipientLookup?.MissingContact,
};
}

return ext;
}
}
66 changes: 48 additions & 18 deletions src/Notifications/API/Mappers/OrderMapper.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -20,20 +18,36 @@
/// </summary>
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<Recipient> recipients =
extRequest.Recipients
.Select(r =>
{
List<IAddressPoint> addresses = new();

var recipients = new List<Recipient>();
if (!string.IsNullOrEmpty(r.EmailAddress))
{
addresses.Add(new EmailAddressPoint(r.EmailAddress));
}

recipients.AddRange(
extRequest.Recipients.Select(r => new Recipient(new List<IAddressPoint>() { new EmailAddressPoint(r.EmailAddress!) })));
return new Recipient(addresses, r.OrganizationNumber, r.NationalIdentityNumber);
})
.ToList();

return new NotificationOrderRequest(
extRequest.SendersReference,
creator,
new(){ emailTemplate },
new List<INotificationTemplate>() { emailTemplate },
extRequest.RequestedSendTime.ToUniversalTime(),
NotificationChannel.Email,
recipients);
recipients,
extRequest.IgnoreReservation,
extRequest.ResourceId);
}

/// <summary>
Expand All @@ -43,18 +57,30 @@
{
INotificationTemplate smsTemplate = new SmsTemplate(extRequest.SenderNumber, extRequest.Body);

List<Recipient> recipients = new();
List<Recipient> recipients =
extRequest.Recipients
.Select(r =>
{
List<IAddressPoint> addresses = new();

recipients.AddRange(
extRequest.Recipients.Select(r => new Recipient(new List<IAddressPoint>() { 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<INotificationTemplate>() { smsTemplate },
extRequest.RequestedSendTime.ToUniversalTime(),
NotificationChannel.Sms,
recipients);
recipients,
extRequest.IgnoreReservation,
extRequest.ResourceId);
}

/// <summary>
Expand All @@ -66,6 +92,7 @@

orderExt.MapBaseNotificationOrder(order);
orderExt.Recipients = order.Recipients.MapToRecipientExt();
orderExt.IgnoreReservation = order.IgnoreReservation;

foreach (var template in order.Templates)
{
Expand Down Expand Up @@ -174,28 +201,31 @@
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;
orderExt.Created = order.Created;
orderExt.Creator = order.Creator.ShortName;
orderExt.NotificationChannel = (NotificationChannelExt)order.NotificationChannel;
orderExt.RequestedSendTime = order.RequestedSendTime;
orderExt.IgnoreReservation = order.IgnoreReservation;
orderExt.ResourceId = order.ResourceId;

return orderExt;
}

private static string? GetEmailFromAddressList(List<IAddressPoint> addressPoints)

Check warning on line 228 in src/Notifications/API/Mappers/OrderMapper.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
var emailAddressPoint = addressPoints
.Find(a => a.AddressType.Equals(AddressType.Email))
Expand All @@ -204,7 +234,7 @@
return emailAddressPoint?.EmailAddress;
}

private static string? GetMobileNumberFromAddressList(List<IAddressPoint> addressPoints)

Check warning on line 237 in src/Notifications/API/Mappers/OrderMapper.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
var smsAddressPoint = addressPoints
.Find(a => a.AddressType.Equals(AddressType.Sms))
Expand Down
61 changes: 61 additions & 0 deletions src/Notifications/API/Models/BaseNotificationOrderExt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System.Text.Json.Serialization;

namespace Altinn.Notifications.Models;

/// <summary>
/// A class representing the base properties of a registered notification order.
/// </summary>
/// <remarks>
/// External representaion to be used in the API.
/// </remarks>
public class BaseNotificationOrderExt
{
/// <summary>
/// Gets or sets the id of the notification order
/// </summary>
[JsonPropertyName("id")]
public string Id { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the senders reference of the notification
/// </summary>
[JsonPropertyName("sendersReference")]
public string? SendersReference { get; set; }

Check warning on line 23 in src/Notifications/API/Models/BaseNotificationOrderExt.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

/// <summary>
/// Gets or sets the requested send time of the notification
/// </summary>
[JsonPropertyName("requestedSendTime")]
public DateTime RequestedSendTime { get; set; }

/// <summary>
/// Gets or sets the short name of the creator of the notification order
/// </summary>
[JsonPropertyName("creator")]
public string Creator { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the date and time of when the notification order was created
/// </summary>
[JsonPropertyName("created")]
public DateTime Created { get; set; }

/// <summary>
/// Gets or sets the preferred notification channel of the notification order
/// </summary>
[JsonPropertyName("notificationChannel")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public NotificationChannelExt NotificationChannel { get; set; }

/// <summary>
/// Gets or sets whether notifications generated by this order should ignore KRR reservations
/// </summary>
[JsonPropertyName("ignoreReservation")]
public bool? IgnoreReservation { get; set; }

/// <summary>
/// Gets or sets the id of the resource that the notification is related to
/// </summary>
[JsonPropertyName("resourceId")]
public string? ResourceId { get; set; }

Check warning on line 60 in src/Notifications/API/Models/BaseNotificationOrderExt.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
}
3 changes: 1 addition & 2 deletions src/Notifications/API/Models/EmailContentTypeExt.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#nullable enable
namespace Altinn.Notifications.Models;
namespace Altinn.Notifications.Models;

/// <summary>
/// Enum describing available content types for an email.
Expand Down
Loading
Loading