diff --git a/src/Altinn.Profile.Core/Integrations/IUnitProfileClient.cs b/src/Altinn.Profile.Core/Integrations/IUnitProfileClient.cs new file mode 100644 index 0000000..500783f --- /dev/null +++ b/src/Altinn.Profile.Core/Integrations/IUnitProfileClient.cs @@ -0,0 +1,17 @@ +using Altinn.Profile.Core.Unit.ContactPoints; + +namespace Altinn.Profile.Core.Integrations +{ + /// + /// Interface describing a client for the user profile service + /// + public interface IUnitProfileClient + { + /// + /// Provides a list of user registered contact points based on the lookup criteria + /// + /// Lookup object containing a list of organizations and a resource + /// A list of unit contact points + Task> GetUserRegisteredContactPoints(UnitContactPointLookup lookup); + } +} diff --git a/src/Altinn.Profile.Core/ServiceCollectionExtensions.cs b/src/Altinn.Profile.Core/ServiceCollectionExtensions.cs index 0b3d3af..da1f54d 100644 --- a/src/Altinn.Profile.Core/ServiceCollectionExtensions.cs +++ b/src/Altinn.Profile.Core/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using Altinn.Profile.Core; +using Altinn.Profile.Core.Unit.ContactPoints; using Altinn.Profile.Core.User; using Altinn.Profile.Core.User.ContactPoints; @@ -24,6 +25,7 @@ public static void AddCoreServices(this IServiceCollection services, IConfigurat .AddMemoryCache() .AddSingleton() .AddSingleton() - .Decorate(); + .Decorate() + .AddSingleton(); } } diff --git a/src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPoints.cs b/src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPoints.cs new file mode 100644 index 0000000..3aec836 --- /dev/null +++ b/src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPoints.cs @@ -0,0 +1,16 @@ +using Altinn.Profile.Core.Unit.ContactPoints; + +namespace Altinn.Profile.Core.User.ContactPoints; + +/// +/// Class describing the methods required for user contact point service +/// +public interface IUnitContactPoints +{ + /// + /// Method for retriveing user registered contact points for a unit + /// + /// A lookup object containing a list of organisation numbers and the resource to lookup contact points for + /// The users' contact points and reservation status or a boolean if failure. + Task> GetUserRegisteredContactPoints(UnitContactPointLookup lookup); +} diff --git a/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointLookup.cs b/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointLookup.cs new file mode 100644 index 0000000..6993da6 --- /dev/null +++ b/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointLookup.cs @@ -0,0 +1,17 @@ +namespace Altinn.Profile.Core.Unit.ContactPoints +{ /// + /// A class describing the query model for contact points for units + /// + public class UnitContactPointLookup + { + /// + /// Gets or sets the list of organisation numbers to lookup contact points for + /// + public List OrganizationNumbers { get; set; } = []; + + /// + /// Gets or sets the resource id to filter the contact points by + /// + public string ResourceId { get; set; } = string.Empty; + } +} diff --git a/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointService.cs b/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointService.cs new file mode 100644 index 0000000..a16ca10 --- /dev/null +++ b/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointService.cs @@ -0,0 +1,28 @@ +using Altinn.Profile.Core.Integrations; +using Altinn.Profile.Core.User.ContactPoints; + +namespace Altinn.Profile.Core.Unit.ContactPoints +{ + /// + /// Implementation of the interface using a REST client to retrieve profile data "/> + /// + public class UnitContactPointService : IUnitContactPoints + { + private readonly IUnitProfileClient _unitClient; + + /// + /// Initializes a new instance of the class. + /// + public UnitContactPointService(IUnitProfileClient unitClient) + { + _unitClient = unitClient; + } + + /// + public async Task> GetUserRegisteredContactPoints(UnitContactPointLookup lookup) + { + Result result = await _unitClient.GetUserRegisteredContactPoints(lookup); + return result; + } + } +} diff --git a/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPoints.cs b/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPoints.cs new file mode 100644 index 0000000..320aaf2 --- /dev/null +++ b/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPoints.cs @@ -0,0 +1,36 @@ +using Altinn.Profile.Core.User.ContactPoints; + +namespace Altinn.Profile.Core.Unit.ContactPoints +{ + /// + /// Class describing the user registered contact points for a unit + /// + public class UnitContactPoints + { + /// + /// Gets or sets the organization number of the organization. + /// + public string OrganizationNumber { get; set; } = string.Empty; + + /// + /// Gets or sets the party id of the organization. + /// + public int PartyId { get; set; } + + /// + /// Gets or sets a list of multiple contanct points associated with the organisation. + /// + public List UserContactPoints { get; set; } = []; + } + + /// + /// A list representation of + /// + public class UnitContactPointsList + { + /// + /// A list containing contact points for users + /// + public List ContactPointsList { get; set; } = []; + } +} diff --git a/src/Altinn.Profile.Core/User.ContactPoints/UserContactPoints.cs b/src/Altinn.Profile.Core/User.ContactPoints/UserContactPoints.cs index e7e335c..f705bfe 100644 --- a/src/Altinn.Profile.Core/User.ContactPoints/UserContactPoints.cs +++ b/src/Altinn.Profile.Core/User.ContactPoints/UserContactPoints.cs @@ -1,7 +1,7 @@ namespace Altinn.Profile.Core.User.ContactPoints; /// -/// Class describing the availability of contact points for a user +/// Class describing the contact points for a user /// public class UserContactPoints { diff --git a/src/Altinn.Profile.Integrations/SblBridge/PartyNotificationContactPoints.cs b/src/Altinn.Profile.Integrations/SblBridge/PartyNotificationContactPoints.cs new file mode 100644 index 0000000..3424883 --- /dev/null +++ b/src/Altinn.Profile.Integrations/SblBridge/PartyNotificationContactPoints.cs @@ -0,0 +1,52 @@ +namespace Altinn.Profile.Integrations.SblBridge +{ + /// + /// Model describing a container for a list of contact points. + /// + public class PartyNotificationContactPoints + { + /// + /// Gets or sets the party id of the organisation. + /// + public Guid? PartyId { get; set; } + + /// + /// Gets or sets the legacy id of the organisation. + /// + public int LegacyPartyId { get; set; } + + /// + /// Gets or sets the organization number of the organisation. + /// + public string OrganizationNumber { get; set; } = string.Empty; + + /// + /// Gets or sets a list of multiple contanct points associated with the organisation. + /// + public List ContactPoints { get; set; } = new List(); + } + + /// + /// Model describing the contact information that a user has associated with a party they can represent. + /// + public class UserRegisteredContactPoint + { + /// + /// Gets or sets the legacy user id for the owner of this party notification endpoint. + /// + /// + /// This was named as legacy for consistency. Property for UUID will probably never be added. + /// + public int LegacyUserId { get; set; } + + /// + /// Gets or sets the email address for this contact point. + /// + public string Email { get; set; } = string.Empty; + + /// + /// Gets or sets the mobile number for this contact point. + /// + public string MobileNumber { get; set; } = string.Empty; + } +} diff --git a/src/Altinn.Profile.Integrations/SblBridge/UnitProfileClient.cs b/src/Altinn.Profile.Integrations/SblBridge/UnitProfileClient.cs new file mode 100644 index 0000000..e3bcf65 --- /dev/null +++ b/src/Altinn.Profile.Integrations/SblBridge/UnitProfileClient.cs @@ -0,0 +1,79 @@ +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +using Altinn.Profile.Core; +using Altinn.Profile.Core.Integrations; +using Altinn.Profile.Core.Unit.ContactPoints; +using Altinn.Profile.Core.User.ContactPoints; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Altinn.Profile.Integrations.SblBridge; + +/// +/// Represents an implementation of using SBLBridge to obtain unit profile information. +/// +public class UnitProfileClient : IUnitProfileClient +{ + private readonly ILogger _logger; + private readonly HttpClient _client; + private readonly JsonSerializerOptions _serializerOptions; + + /// + /// Initializes a new instance of the class + /// + /// HttpClient from default http client factory + /// the logger + /// the sbl bridge settings + public UnitProfileClient( + HttpClient httpClient, + ILogger logger, + IOptions settings) + { + _logger = logger; + _client = httpClient; + _client.BaseAddress = new Uri(settings.Value.ApiProfileEndpoint); + + _serializerOptions = new JsonSerializerOptions + { + WriteIndented = true, + PropertyNameCaseInsensitive = true, + Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) } + }; + } + + /// + public async Task> GetUserRegisteredContactPoints(UnitContactPointLookup lookup) + { + string endpoint = $"units/contactpointslookup"; + + StringContent requestBody = new StringContent(JsonSerializer.Serialize(lookup), Encoding.UTF8, "application/json"); + + HttpResponseMessage response = await _client.PostAsync(endpoint, requestBody); + + if (!response.IsSuccessStatusCode) + { + _logger.LogError("// UnitClient // GetUserRegisteredContactPoints // An error occured when retrieving unit contact points. Failed with {statusCode}", response.StatusCode); + return false; + } + + string content = await response.Content.ReadAsStringAsync(); + List partyNotificationEndpoints = JsonSerializer.Deserialize>(content, _serializerOptions)!; + + List contactPoints = partyNotificationEndpoints.Select(partyNotificationEndpoint => new UnitContactPoints + { + OrganizationNumber = partyNotificationEndpoint.OrganizationNumber, + PartyId = partyNotificationEndpoint.LegacyPartyId, + UserContactPoints = partyNotificationEndpoint.ContactPoints.Select(contactPoint => new UserContactPoints + { + UserId = contactPoint.LegacyUserId, + Email = contactPoint.Email, + MobileNumber = contactPoint.MobileNumber + }).ToList() + }).ToList(); + + return new UnitContactPointsList() { ContactPointsList = contactPoints }; + } +} diff --git a/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs b/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs index f1ee636..703128c 100644 --- a/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs +++ b/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs @@ -22,8 +22,8 @@ public static void AddSblBridgeClients(this IServiceCollection services, IConfig .Get() ?? throw new ArgumentNullException(nameof(config), "Required SblBridgeSettings is missing from application configuration"); - services - .Configure(config.GetSection(nameof(SblBridgeSettings))) - .AddHttpClient(); + services.Configure(config.GetSection(nameof(SblBridgeSettings))); + services.AddHttpClient(); + services.AddHttpClient(); } } diff --git a/src/Altinn.Profile/Controllers/UnitContactPointController.cs b/src/Altinn.Profile/Controllers/UnitContactPointController.cs new file mode 100644 index 0000000..ca87761 --- /dev/null +++ b/src/Altinn.Profile/Controllers/UnitContactPointController.cs @@ -0,0 +1,45 @@ +using System.Threading.Tasks; + +using Altinn.Profile.Core; +using Altinn.Profile.Core.Unit.ContactPoints; +using Altinn.Profile.Core.User.ContactPoints; + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Altinn.Profile.Controllers +{ + /// + /// Controller for unit profile contact point API endpoints for internal consumption (e.g. Notifications) requiring neither authenticated user token nor access token authorization. + /// + [Route("profile/api/v1/units/contactpoint")] + [ApiExplorerSettings(IgnoreApi = true)] + [Consumes("application/json")] + [Produces("application/json")] + public class UnitContactPointController : ControllerBase + { + private readonly IUnitContactPoints _contactPointService; + + /// + /// Initializes a new instance of the class. + /// + public UnitContactPointController(IUnitContactPoints contactPointsService) + { + _contactPointService = contactPointsService; + } + + /// + /// Endpoint looking up the contact points for the units provided in the lookup object in the request body + /// + /// Returns an overview of the user registered contact points for the provided units and given resource + [HttpPost("lookup")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> PostLookup([FromBody] UnitContactPointLookup unitContactPointLookup) + { + Result result = await _contactPointService.GetUserRegisteredContactPoints(unitContactPointLookup); + return result.Match>( + success => Ok(success), + _ => Problem("Could not retrieve contact points")); + } + } +} diff --git a/src/Altinn.Profile/Controllers/UserContactPointController.cs b/src/Altinn.Profile/Controllers/UserContactPointController.cs index c11722f..d45d328 100644 --- a/src/Altinn.Profile/Controllers/UserContactPointController.cs +++ b/src/Altinn.Profile/Controllers/UserContactPointController.cs @@ -2,6 +2,7 @@ using Altinn.Profile.Core; using Altinn.Profile.Core.User.ContactPoints; +using Altinn.Profile.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; diff --git a/src/Altinn.Profile.Core/User.ContactPoints/UserContactPointLookup.cs b/src/Altinn.Profile/Models/UserContactPointLookup.cs similarity index 82% rename from src/Altinn.Profile.Core/User.ContactPoints/UserContactPointLookup.cs rename to src/Altinn.Profile/Models/UserContactPointLookup.cs index f3db92f..1451e30 100644 --- a/src/Altinn.Profile.Core/User.ContactPoints/UserContactPointLookup.cs +++ b/src/Altinn.Profile/Models/UserContactPointLookup.cs @@ -1,4 +1,6 @@ -namespace Altinn.Profile.Core.User.ContactPoints; +using System.Collections.Generic; + +namespace Altinn.Profile.Models; /// /// A class respresenting a user contact point lookup object diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/UserContactPointControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/UserContactPointControllerTests.cs index beea3ee..4fd4299 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/UserContactPointControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/UserContactPointControllerTests.cs @@ -9,6 +9,7 @@ using Altinn.Profile.Controllers; using Altinn.Profile.Core.User.ContactPoints; using Altinn.Profile.Integrations.SblBridge; +using Altinn.Profile.Models; using Altinn.Profile.Tests.IntegrationTests.Mocks; using Altinn.Profile.Tests.IntegrationTests.Utils; using Altinn.Profile.Tests.Testdata;