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

Feature/508 notification with resource #517

Merged
merged 44 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
4bf00ac
Add authorization service
SandGrainOne May 3, 2024
618d8be
Merge branch 'main' into feature/472-Access-filtering-of-recipients
SandGrainOne May 7, 2024
6988b98
current progress
acn-sbuad May 7, 2024
1d73fa2
reinstated authorization in controller
acn-sbuad May 7, 2024
4752ab8
Merge branch 'main' into feature/508-notification-with-resource
acn-sbuad May 8, 2024
5f65e75
fixed code smells and duplication
acn-sbuad May 8, 2024
c3c7811
moved serialization out of base class
acn-sbuad May 8, 2024
b6fb345
using find instead of firstordefault
acn-sbuad May 8, 2024
f438b23
Authorization for multiple users in many organizations
SandGrainOne May 10, 2024
bc3ba97
Merge appsettings
SandGrainOne May 10, 2024
4bddd5b
Add tests and remove test controller
SandGrainOne May 14, 2024
bb92ecd
Merge branch 'main' into feature/472-Access-filtering-of-recipients
SandGrainOne May 14, 2024
e22f45a
Replace ContainsKey with TryGetValue
SandGrainOne May 14, 2024
a876e66
Move authorization interface and service
SandGrainOne May 15, 2024
1bdf71a
Expand OrganizationContactPoint with personal contact info
SandGrainOne May 15, 2024
b6a243b
Move test class for AuthorizationService
SandGrainOne May 15, 2024
4aba2dd
Rename AuthorizationService to client
SandGrainOne May 15, 2024
62b00d9
Change input type
SandGrainOne May 15, 2024
777b7ac
Fix comments and settings
SandGrainOne May 16, 2024
35240e8
Move setup, rename again
SandGrainOne May 16, 2024
1b0bb72
Change Authorization return type
SandGrainOne May 21, 2024
4973fae
merged authorization logic
acn-sbuad May 22, 2024
c8333dd
Fixes based on feedback
SandGrainOne May 22, 2024
8f732b1
Move test class AuthorizationServiceTests
SandGrainOne May 22, 2024
a285c69
Change clone of OrganizationContactPoints
SandGrainOne May 22, 2024
05dac52
current progress
acn-sbuad May 22, 2024
d380f7f
Merge branch 'feature/472-Access-filtering-of-recipients' into featur…
acn-sbuad May 22, 2024
e1db20e
tests running for profile client
acn-sbuad May 22, 2024
5d01019
cleaned up k6 file obsolete props
acn-sbuad May 22, 2024
096be10
added test for authorization result merge
acn-sbuad May 23, 2024
a2c90a3
merged main
acn-sbuad May 23, 2024
d33efc6
removed obsolete file
acn-sbuad May 23, 2024
6b9aea8
adjustet test to cover sms
acn-sbuad May 23, 2024
3c23e6a
fixed indentation
acn-sbuad May 23, 2024
5cdb0c6
Fixed errors in code
acn-sbuad May 23, 2024
cc325a7
fixed return type for profile api response
acn-sbuad May 23, 2024
06ffff9
GetUserRegisteredOrganizationContactPoints -> GetUserRegisteredContac…
acn-sbuad May 24, 2024
7ff58d8
Update src/Altinn.Notifications.Core/Integrations/IProfileClient.cs
acn-sbuad May 27, 2024
9aa334c
fixed some pr comments
acn-sbuad May 27, 2024
3f69a32
Merge branch 'feature/508-notification-with-resource' of https://gith…
acn-sbuad May 27, 2024
c9b48c1
set parameters as required
acn-sbuad May 27, 2024
f6cfac3
added required to sms model
acn-sbuad May 27, 2024
e704834
fixed code smell async validation
acn-sbuad May 27, 2024
9832061
rolled back async validation. Caused null reference exception
acn-sbuad May 28, 2024
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
8 changes: 8 additions & 0 deletions src/Altinn.Notifications.Core/Integrations/IProfileClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,12 @@ public interface IProfileClient
/// <param name="nationalIdentityNumbers">A list of national identity numbers to look up contact points for</param>
/// <returns>A list of contact points for the provided national identity numbers </returns>
public Task<List<UserContactPoints>> GetUserContactPoints(List<string> nationalIdentityNumbers);

/// <summary>
/// Retrieves the user registered contact points for a list of organizations identified by organization numbers
/// </summary>
/// <param name="organizationNumbers">The set or organizations to retrieve contact points for</param>
/// <param name="resourceId">The id of the resource to look up contact points for</param>
/// <returns>A list of organiation contact points containing user registered contact points</returns>
public Task<List<OrganizationContactPoints>> GetUserRegisteredContactPoints(List<string> organizationNumbers, string resourceId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ public interface IBaseNotificationOrder
/// </summary>
public bool IgnoreReservation { get; }

/// <summary>
/// Gets or sets the id of the resource that the notification is related to
/// </summary>
public string? ResourceId { get; }

/// <summary>
/// Gets the creator of the notification
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
/// <inheritdoc/>>
public bool IgnoreReservation { get; internal set; }

/// <inheritdoc/>>
public string? ResourceId { get; internal set; }

/// <inheritdoc/>>
public Creator Creator { get; internal set; }

Expand All @@ -44,7 +47,7 @@
/// <summary>
/// Initializes a new instance of the <see cref="NotificationOrder"/> class.
/// </summary>
public NotificationOrder(

Check warning on line 50 in src/Altinn.Notifications.Core/Models/Orders/NotificationOrder.cs

View workflow job for this annotation

GitHub Actions / Build, test & analyze

Constructor has 10 parameters, which is greater than the 7 authorized. (https://rules.sonarsource.com/csharp/RSPEC-107)

Check warning on line 50 in src/Altinn.Notifications.Core/Models/Orders/NotificationOrder.cs

View workflow job for this annotation

GitHub Actions / Build, test & analyze

Constructor has 10 parameters, which is greater than the 7 authorized. (https://rules.sonarsource.com/csharp/RSPEC-107)
Guid id,
string? sendersReference,
List<INotificationTemplate> templates,
Expand All @@ -53,7 +56,8 @@
Creator creator,
DateTime created,
List<Recipient> recipients,
bool ignoreReservation)
bool ignoreReservation,
string? resourceId)
{
Id = id;
SendersReference = sendersReference;
Expand All @@ -64,6 +68,7 @@
Created = created;
Recipients = recipients;
IgnoreReservation = ignoreReservation;
ResourceId = resourceId;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,27 @@
public Creator Creator { get; internal set; }

/// <summary>
/// Gets or sets whether notifications generated by this order should ignore KRR reservations
/// Gets a boolean indicating whether notifications generated by this order should ignore KRR reservations
/// </summary>
public bool IgnoreReservation { get; internal set; }

/// <summary>
/// Gets the id of the resource that the notification is related to
/// </summary>
public string? ResourceId { get; internal set; }

/// <summary>
/// Initializes a new instance of the <see cref="NotificationOrderRequest"/> class.
/// </summary>
public NotificationOrderRequest(

Check warning on line 54 in src/Altinn.Notifications.Core/Models/Orders/NotificationOrderRequest.cs

View workflow job for this annotation

GitHub Actions / Build, test & analyze

Constructor has 8 parameters, which is greater than the 7 authorized. (https://rules.sonarsource.com/csharp/RSPEC-107)

Check warning on line 54 in src/Altinn.Notifications.Core/Models/Orders/NotificationOrderRequest.cs

View workflow job for this annotation

GitHub Actions / Build, test & analyze

Constructor has 8 parameters, which is greater than the 7 authorized. (https://rules.sonarsource.com/csharp/RSPEC-107)
string? sendersReference,
string creatorShortName,
List<INotificationTemplate> templates,
DateTime requestedSendTime,
NotificationChannel notificationChannel,
List<Recipient> recipients,
bool ignoreReservation = false)
bool ignoreReservation = false,
string? resourceId = null)
{
SendersReference = sendersReference;
Creator = new(creatorShortName);
Expand All @@ -62,6 +68,7 @@
NotificationChannel = notificationChannel;
Recipients = recipients;
IgnoreReservation = ignoreReservation;
ResourceId = resourceId;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public class NotificationOrderWithStatus : IBaseNotificationOrder
/// <inheritdoc/>>
public bool IgnoreReservation { get; internal set; }

/// <inheritdoc/>>
public string? ResourceId { get; internal set; }

/// <summary>
/// Gets the processing status of the notication order
/// </summary>
Expand Down
68 changes: 58 additions & 10 deletions src/Altinn.Notifications.Core/Services/ContactPointService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,24 @@ public class ContactPointService : IContactPointService
{
private readonly IProfileClient _profileClient;
private readonly IRegisterClient _registerClient;
private readonly IAuthorizationService _authorizationService;

/// <summary>
/// Initializes a new instance of the <see cref="ContactPointService"/> class.
/// </summary>
public ContactPointService(IProfileClient profile, IRegisterClient register)
public ContactPointService(IProfileClient profile, IRegisterClient register, IAuthorizationService authorizationService)
{
_profileClient = profile;
_registerClient = register;
_authorizationService = authorizationService;
}

/// <inheritdoc/>
public async Task AddEmailContactPoints(List<Recipient> recipients)
public async Task AddEmailContactPoints(List<Recipient> recipients, string? resourceId)
{
await AugmentRecipients(
recipients,
resourceId,
(recipient, userContactPoints) =>
{
if (!string.IsNullOrEmpty(userContactPoints.Email))
Expand All @@ -40,16 +43,24 @@ await AugmentRecipients(
},
(recipient, orgContactPoints) =>
{
recipient.AddressInfo.AddRange(orgContactPoints.EmailList.Select(e => new EmailAddressPoint(e)).ToList());
recipient.AddressInfo.AddRange(orgContactPoints.EmailList
.Select(e => new EmailAddressPoint(e))
.ToList());

recipient.AddressInfo.AddRange(orgContactPoints.UserContactPoints
.Where(u => !string.IsNullOrEmpty(u.Email))
.Select(u => new EmailAddressPoint(u.Email))
.ToList());
return recipient;
});
}

/// <inheritdoc/>
public async Task AddSmsContactPoints(List<Recipient> recipients)
public async Task AddSmsContactPoints(List<Recipient> recipients, string? resourceId)
{
await AugmentRecipients(
recipients,
resourceId,
(recipient, userContactPoints) =>
{
if (!string.IsNullOrEmpty(userContactPoints.MobileNumber))
Expand All @@ -61,20 +72,28 @@ await AugmentRecipients(
},
(recipient, orgContactPoints) =>
{
recipient.AddressInfo.AddRange(orgContactPoints.MobileNumberList.Select(m => new SmsAddressPoint(m)).ToList());
recipient.AddressInfo.AddRange(orgContactPoints.MobileNumberList
.Select(m => new SmsAddressPoint(m))
.ToList());

recipient.AddressInfo.AddRange(orgContactPoints.UserContactPoints
.Where(u => !string.IsNullOrEmpty(u.MobileNumber))
.Select(u => new SmsAddressPoint(u.MobileNumber))
.ToList());
return recipient;
});
}

private async Task<List<Recipient>> AugmentRecipients(
List<Recipient> recipients,
string? resourceId,
Func<Recipient, UserContactPoints, Recipient> createUserContactPoint,
Func<Recipient, OrganizationContactPoints, Recipient> createOrgContactPoint)
{
List<Recipient> augmentedRecipients = [];

var userLookupTask = LookupPersonContactPoints(recipients);
var orgLookupTask = LookupOrganizationContactPoints(recipients);
var orgLookupTask = LookupOrganizationContactPoints(recipients, resourceId);
await Task.WhenAll(userLookupTask, orgLookupTask);

List<UserContactPoints> userContactPointsList = userLookupTask.Result;
Expand Down Expand Up @@ -130,10 +149,8 @@ private async Task<List<UserContactPoints>> LookupPersonContactPoints(List<Recip
return contactPoints;
}

private async Task<List<OrganizationContactPoints>> LookupOrganizationContactPoints(List<Recipient> recipients)
private async Task<List<OrganizationContactPoints>> LookupOrganizationContactPoints(List<Recipient> recipients, string? resourceId)
{
/* the output from this function should include an AUHTORIZED list of user registered contact points if notification has a service affiliation
will require the extension of the OrganizationContactPoints class */
List<string> orgNos = recipients
.Where(r => !string.IsNullOrEmpty(r.OrganizationNumber))
.Select(r => r.OrganizationNumber!)
Expand All @@ -144,7 +161,38 @@ will require the extension of the OrganizationContactPoints class */
return [];
}

List<OrganizationContactPoints> contactPoints = await _registerClient.GetOrganizationContactPoints(orgNos);
Task<List<OrganizationContactPoints>> registerTask = _registerClient.GetOrganizationContactPoints(orgNos);
List<OrganizationContactPoints> authorizedUserContactPoints = new();

if (!string.IsNullOrEmpty(resourceId))
{
var allUserContactPoints = await _profileClient.GetUserRegisteredContactPoints(orgNos, resourceId);
authorizedUserContactPoints = await _authorizationService.AuthorizeUserContactPointsForResource(allUserContactPoints, resourceId);
}

List<OrganizationContactPoints> contactPoints = await registerTask;

if (!string.IsNullOrEmpty(resourceId))
{
foreach (var userContactPoint in authorizedUserContactPoints)
{
userContactPoint.UserContactPoints.ForEach(userContactPoint =>
{
userContactPoint.MobileNumber = MobileNumberHelper.EnsureCountryCodeIfValidNumber(userContactPoint.MobileNumber);
});

var existingContactPoint = contactPoints.Find(cp => cp.OrganizationNumber == userContactPoint.OrganizationNumber);

if (existingContactPoint != null)
{
existingContactPoint.UserContactPoints.AddRange(userContactPoint.UserContactPoints);
}
else
{
contactPoints.Add(userContactPoint);
}
}
}

contactPoints.ForEach(contactPoint =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public async Task ProcessOrder(NotificationOrder order)
var recipients = order.Recipients;
var recipientsWithoutEmail = recipients.Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Email)).ToList();

await _contactPointService.AddEmailContactPoints(recipientsWithoutEmail);
await _contactPointService.AddEmailContactPoints(recipientsWithoutEmail, order.ResourceId);

foreach (Recipient recipient in recipients)
{
Expand All @@ -50,7 +50,7 @@ public async Task ProcessOrderRetry(NotificationOrder order)
var recipients = order.Recipients;
var recipientsWithoutEmail = recipients.Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Email)).ToList();

await _contactPointService.AddEmailContactPoints(recipientsWithoutEmail);
await _contactPointService.AddEmailContactPoints(recipientsWithoutEmail, order.ResourceId);

List<EmailRecipient> emailRecipients = await _emailNotificationRepository.GetRecipients(order.Id);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@ public interface IContactPointService
/// Looks up and adds the email contact points for recipients based on their national identity number or organization number
/// </summary>
/// <param name="recipients">List of recipients to retrieve contact points for</param>
/// <param name="resourceId">The resource to find contact points in relation to</param>
/// <returns>The list of recipients augumented with email address points where available</returns>
/// <remarks>Implementation alters the recipient reference object directly</remarks>
public Task AddEmailContactPoints(List<Recipient> recipients);
public Task AddEmailContactPoints(List<Recipient> recipients, string? resourceId);

/// <summary>
/// Looks up and adds the SMS contact points for recipients based on their national identity number or organization number
/// </summary>
/// <param name="recipients">List of recipients to retrieve contact points for</param>
/// <param name="resourceId">The resource to find contact points in relation to</param>
/// <returns>The list of recipients augumented with SMS address points where available</returns>
/// <remarks>Implementation alters the recipient reference object directly</remarks>
public Task AddSmsContactPoints(List<Recipient> recipients);
public Task AddSmsContactPoints(List<Recipient> recipients, string? resourceId);
}
}
11 changes: 6 additions & 5 deletions src/Altinn.Notifications.Core/Services/OrderRequestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public async Task<NotificationOrderRequestResponse> RegisterNotificationOrder(No

// copying recipients by value to not alter the orderRequest that will be persisted
var copiedRecipents = orderRequest.Recipients.Select(r => r.DeepCopy()).ToList();
var lookupResult = await GetRecipientLookupResult(copiedRecipents, orderRequest.NotificationChannel);
var lookupResult = await GetRecipientLookupResult(copiedRecipents, orderRequest.NotificationChannel, orderRequest.ResourceId);

var templates = SetSenderIfNotDefined(orderRequest.Templates);

Expand All @@ -62,7 +62,8 @@ public async Task<NotificationOrderRequestResponse> RegisterNotificationOrder(No
orderRequest.Creator,
created,
orderRequest.Recipients,
orderRequest.IgnoreReservation);
orderRequest.IgnoreReservation,
orderRequest.ResourceId);

NotificationOrder savedOrder = await _repository.Create(order);

Expand All @@ -73,7 +74,7 @@ public async Task<NotificationOrderRequestResponse> RegisterNotificationOrder(No
};
}

private async Task<RecipientLookupResult?> GetRecipientLookupResult(List<Recipient> recipients, NotificationChannel channel)
private async Task<RecipientLookupResult?> GetRecipientLookupResult(List<Recipient> recipients, NotificationChannel channel, string? resourceId)
{
List<Recipient> recipientsWithoutContactPoint = [];

Expand All @@ -96,11 +97,11 @@ public async Task<NotificationOrderRequestResponse> RegisterNotificationOrder(No

if (channel == NotificationChannel.Email)
{
await _contactPointService.AddEmailContactPoints(recipientsWithoutContactPoint);
await _contactPointService.AddEmailContactPoints(recipientsWithoutContactPoint, resourceId);
}
else if (channel == NotificationChannel.Sms)
{
await _contactPointService.AddSmsContactPoints(recipientsWithoutContactPoint);
await _contactPointService.AddSmsContactPoints(recipientsWithoutContactPoint, resourceId);
}

var isReserved = recipients.Where(r => r.IsReserved.HasValue && r.IsReserved.Value).Select(r => r.NationalIdentityNumber!).ToList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public async Task ProcessOrder(NotificationOrder order)
{
var recipients = order.Recipients;
var recipientsWithoutMobileNumber = recipients.Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Sms)).ToList();
await _contactPointService.AddSmsContactPoints(recipientsWithoutMobileNumber);
await _contactPointService.AddSmsContactPoints(recipientsWithoutMobileNumber, order.ResourceId);

int smsCount = GetSmsCountForOrder(order);

Expand All @@ -52,7 +52,7 @@ public async Task ProcessOrderRetry(NotificationOrder order)
var recipients = order.Recipients;
var recipientsWithoutMobileNumber = recipients.Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Sms)).ToList();

await _contactPointService.AddSmsContactPoints(recipientsWithoutMobileNumber);
await _contactPointService.AddSmsContactPoints(recipientsWithoutMobileNumber, order.ResourceId);

int smsCount = GetSmsCountForOrder(order);
List<SmsRecipient> smsRecipients = await _smsNotificationRepository.GetRecipients(order.Id);
Expand Down
27 changes: 26 additions & 1 deletion src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Altinn.Notifications.Core.Shared;
using Altinn.Notifications.Integrations.Configuration;
using Altinn.Notifications.Integrations.Profile;
using Altinn.Notifications.Integrations.Register;

using Microsoft.Extensions.Options;

Expand Down Expand Up @@ -46,7 +47,31 @@ public async Task<List<UserContactPoints>> GetUserContactPoints(List<string> nat
}

string responseContent = await response.Content.ReadAsStringAsync();
List<UserContactPoints>? contactPoints = JsonSerializer.Deserialize<UserContactPointsList>(responseContent, JsonSerializerOptionsProvider.Options)!.ContactPointsList;
List<UserContactPoints> contactPoints = JsonSerializer.Deserialize<UserContactPointsList>(responseContent, JsonSerializerOptionsProvider.Options)!.ContactPointsList;
return contactPoints!;
}

/// <inheritdoc/>
public async Task<List<OrganizationContactPoints>> GetUserRegisteredContactPoints(List<string> organizationNumbers, string resourceId)
{
var lookupObject = new UnitContactPointLookup()
{
ResourceId = resourceId,
OrganizationNumbers = organizationNumbers
};

HttpContent content = new StringContent(JsonSerializer.Serialize(lookupObject, JsonSerializerOptionsProvider.Options), Encoding.UTF8, "application/json");

var response = await _client.PostAsync("units/contactpoint/lookup", content);

if (!response.IsSuccessStatusCode)
{
throw new PlatformHttpException(response, $"ProfileClient.GetUserRegisteredContactPoints failed with status code {response.StatusCode}");
}

string responseContent = await response.Content.ReadAsStringAsync();
OrgContactPointsList contactPoints = JsonSerializer.Deserialize<OrgContactPointsList>(responseContent, JsonSerializerOptionsProvider.Options)!;

return contactPoints.ContactPointsList;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Altinn.Notifications.Integrations.Profile
{
/// <summary>
/// A class describing the query model for contact points for units
/// </summary>
public class UnitContactPointLookup
{
/// <summary>
/// Gets or sets the list of organisation numbers to lookup contact points for
/// </summary>
public List<string> OrganizationNumbers { get; set; } = [];

/// <summary>
/// Gets or sets the resource id to filter the contact points by
/// </summary>
public string ResourceId { get; set; } = string.Empty;
}
}
Loading
Loading