Skip to content

Commit

Permalink
Add an endpoint to synchronize the KKR database with updates from the…
Browse files Browse the repository at this point in the history
… change log (#221)

Adding client code for integration with the contacts and reservations registry.
Adding necessary code to store changed registry entries.
Adding an endpoint for triggering a synchronization operation.
---------

Co-authored-by: Hallgeir Garnes-Gutvik <hallgeir.garnes-gutvik@digdir.no>
Co-authored-by: Terje Holene <terje.holene@gmail.com>
  • Loading branch information
3 people authored Oct 25, 2024
1 parent da804fd commit e75d02a
Show file tree
Hide file tree
Showing 66 changed files with 2,309 additions and 1,571 deletions.
21 changes: 21 additions & 0 deletions .github/workflows/build-and-analyze.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ jobs:
name: Build, test & analyze
if: ((github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) || github.event_name == 'push') && github.repository_owner == 'Altinn'
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: platform_profile_admin
POSTGRES_PASSWORD: Password
POSTGRES_DB: profiledb
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- name: Setup .NET
uses: actions/setup-dotnet@v4
Expand All @@ -28,6 +42,13 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Setup PostgreSQL
run: |
chmod +x dbsetup.sh
./dbsetup.sh
- name: Restart database to enable config changes
run: |
docker restart $(docker ps -q)
- name: Cache SonarCloud packages
uses: actions/cache@v4
with:
Expand Down
13 changes: 13 additions & 0 deletions dbsetup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
export PGPASSWORD=Password

# alter max connections
psql -h localhost -p 5432 -U platform_profile_admin -d profiledb \
-c "ALTER SYSTEM SET max_connections TO '200';"

# set up platform_profile role
psql -h localhost -p 5432 -U platform_profile_admin -d profiledb \
-c "DO \$\$
BEGIN CREATE ROLE platform_profile WITH LOGIN PASSWORD 'Password';
EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
END \$\$;"
5 changes: 1 addition & 4 deletions src/Altinn.Profile.Core/Altinn.Profile.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@


<ItemGroup>
<PackageReference Include="Altinn.ApiClients.Maskinporten" Version="9.2.0" />
<PackageReference Include="Altinn.Platform.Models" Version="1.6.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageReference Include="Scrutor" Version="5.0.1" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Collections.Immutable;
using System.Text.Json.Serialization;

using Altinn.Profile.Core.Person.ContactPreferences;

namespace Altinn.Profile.Core.ContactRegister;

/// <summary>
/// Represents the changes to a person's contact preferences from the contact register.
/// </summary>
public record ContactRegisterChangesLog
{
/// <summary>
/// Gets the collection of snapshots representing the changes to a person's contact preferences.
/// </summary>
[JsonPropertyName("list")]
public IImmutableList<PersonContactPreferencesSnapshot>? ContactPreferencesSnapshots { get; init; }

/// <summary>
/// Gets the ending change identifier, which indicates the point at which the system should stop retrieving changes.
/// </summary>
[JsonPropertyName("tilEndringsId")]
public long? EndingIdentifier { get; init; }

/// <summary>
/// Gets the most recent change identifier, which represents the last change that was processed by the system.
/// </summary>
[JsonPropertyName("sisteEndringsId")]
public long? LatestChangeIdentifier { get; init; }

/// <summary>
/// Gets the starting change identifier indicating the point from which the system begins retrieving changes.
/// </summary>
[JsonPropertyName("fraEndringsId")]
public long? StartingIdentifier { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Altinn.Profile.Core.ContactRegister;

namespace Altinn.Profile.Integrations.ContactRegister;

/// <summary>
/// An HTTP client to interact with the contact register.
/// </summary>
public interface IContactRegisterHttpClient
{
/// <summary>
/// Retrieves the changes in persons' contact details from the specified endpoint.
/// </summary>
/// <param name="endpointUrl">The URL of the endpoint to retrieve contact details changes from.</param>
/// <param name="startingIdentifier">The starting identifier for retrieving contact details changes.</param>
/// <returns>
/// A task that represents the asynchronous operation.
/// </returns>
Task<ContactRegisterChangesLog> GetContactDetailsChangesAsync(string endpointUrl, long startingIdentifier);
}
16 changes: 16 additions & 0 deletions src/Altinn.Profile.Core/ContactRegister/IContactRegisterService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Altinn.Profile.Core.ContactRegister;

/// <summary>
/// Interface for handling changes in a person's contact preferences.
/// </summary>
public interface IContactRegisterService
{
/// <summary>
/// Asynchronously retrieves the changes in contact preferences for all persons starting from a given number.
/// </summary>
/// <param name="startingIdentifier">The identifier from which to start retrieving the data.</param>
/// <returns>
/// A task that represents the asynchronous operation.
/// </returns>
Task<ContactRegisterChangesLog> RetrieveContactDetailsChangesAsync(long startingIdentifier);
}
93 changes: 0 additions & 93 deletions src/Altinn.Profile.Core/Domain/IRepository.cs

This file was deleted.

72 changes: 47 additions & 25 deletions src/Altinn.Profile.Core/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,28 @@ namespace Altinn.Profile.Core.Extensions;
/// </summary>
public static partial class StringExtensions
{
/// <summary>
/// Validates the given URL.
/// </summary>
/// <param name="url">The URL to validate.</param>
/// <returns>True if the URL is valid; otherwise, false.</returns>
public static bool IsValidUrl(this string url)
{
if (string.IsNullOrWhiteSpace(url))
{
return false;
}

try
{
return Uri.TryCreate(url, UriKind.Absolute, out Uri? uriResult) && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);
}
catch (UriFormatException)
{
return false;
}
}

/// <summary>
/// Determines whether a given string consists of only digits.
/// </summary>
Expand Down Expand Up @@ -49,48 +71,48 @@ public static bool IsDigitsOnly(this string input)
}

/// <summary>
/// Determines whether a given string represents a valid format for a Norwegian Social Security Number (SSN).
/// Determines whether a given string represents a valid format for a Norwegian national identity mumber.
/// </summary>
/// <param name="socialSecurityNumber">The Norwegian Social Security Number (SSN) to validate.</param>
/// <param name="nationalIdentityNumber">The Norwegian national identity number to validate.</param>
/// <param name="controlDigits">Indicates whether to validate the control digits.</param>
/// <returns>
/// <c>true</c> if the given string represents a valid format for a Norwegian Social Security Number (SSN) and, if specified, the control digits are valid; otherwise, <c>false</c>.
/// <c>true</c> if the given string represents a valid format for a Norwegian national identity number and, if specified, the control digits are valid; otherwise, <c>false</c>.
/// </returns>
/// <remarks>
/// A valid Norwegian Social Security Number (SSN) is an 11-digit number where:
/// A valid Norwegian national identity number is an 11-digit number where:
/// - The first six digits represent the date of birth in the format DDMMYY.
/// - The next three digits are an individual number where the first digit indicates the century of birth.
/// - The last two digits are control digits.
/// </remarks>
/// <exception cref="FormatException">Thrown when the individual number part of the SSN cannot be parsed into an integer.</exception>
/// <exception cref="FormatException">Thrown when the individual number part of the national identity number cannot be parsed into an integer.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the parsed date is outside the range of DateTime.</exception>
public static bool IsValidSocialSecurityNumber(this string socialSecurityNumber, bool controlDigits = true)
public static bool IsValidNationalIdentityNumber(this string nationalIdentityNumber, bool controlDigits = true)
{
if (string.IsNullOrWhiteSpace(socialSecurityNumber) || socialSecurityNumber.Length != 11)
if (string.IsNullOrWhiteSpace(nationalIdentityNumber) || nationalIdentityNumber.Length != 11)
{
return false;
}

// Return the cached result if the given string has been checked once.
if (CachedSocialSecurityNumber.TryGetValue(socialSecurityNumber, out var cachedResult))
if (CachedNationalIdentityNumber.TryGetValue(nationalIdentityNumber, out var cachedResult))
{
return cachedResult;
}

ReadOnlySpan<char> socialSecurityNumberSpan = socialSecurityNumber.AsSpan();
ReadOnlySpan<char> nationalIdentityNumberSpan = nationalIdentityNumber.AsSpan();

for (int i = 0; i < socialSecurityNumberSpan.Length; i++)
for (int i = 0; i < nationalIdentityNumberSpan.Length; i++)
{
if (!char.IsDigit(socialSecurityNumberSpan[i]))
if (!char.IsDigit(nationalIdentityNumberSpan[i]))
{
return false;
}
}

// Extract parts of the Social Security Number (SSN) using slicing.
ReadOnlySpan<char> datePart = socialSecurityNumberSpan[..6];
ReadOnlySpan<char> controlDigitsPart = socialSecurityNumberSpan[9..11];
ReadOnlySpan<char> individualNumberPart = socialSecurityNumberSpan[6..9];
// Extract parts of the national identity number using slicing.
ReadOnlySpan<char> datePart = nationalIdentityNumberSpan[..6];
ReadOnlySpan<char> individualNumberPart = nationalIdentityNumberSpan[6..9];
ReadOnlySpan<char> controlDigitsPart = nationalIdentityNumberSpan[9..11];

// If parsing the individual number part fails, return false.
if (!int.TryParse(individualNumberPart, out _))
Expand All @@ -104,17 +126,17 @@ public static bool IsValidSocialSecurityNumber(this string socialSecurityNumber,
return false;
}

var isValidSocialSecurityNumber = !controlDigits || CalculateControlDigits(socialSecurityNumberSpan[..9].ToString()) == controlDigitsPart.ToString();
var isValidNationalIdentityNumber = !controlDigits || CalculateControlDigits(nationalIdentityNumberSpan[..9].ToString()) == controlDigitsPart.ToString();

CachedSocialSecurityNumber.TryAdd(socialSecurityNumber, isValidSocialSecurityNumber);
CachedNationalIdentityNumber.TryAdd(nationalIdentityNumber, isValidNationalIdentityNumber);

return isValidSocialSecurityNumber;
return isValidNationalIdentityNumber;
}

/// <summary>
/// Calculates the control digits used to validate a Norwegian Social Security Number.
/// Calculates the control digits used to validate a Norwegian national identity number.
/// </summary>
/// <param name="firstNineDigits">The first nine digits of the Social Security Number.</param>
/// <param name="firstNineDigits">The first nine digits of the national identity number.</param>
/// <returns>A <see cref="string"/> represents the two control digits.</returns>
private static string CalculateControlDigits(string firstNineDigits)
{
Expand Down Expand Up @@ -167,12 +189,12 @@ private static int CalculateControlDigit(string digits, int[] weights)
private static partial Regex DigitsOnlyRegex();

/// <summary>
/// A cache for storing the validation results of Norwegian Social Security Numbers (SSNs).
/// A cache for storing the validation results of Norwegian national identity numbers.
/// </summary>
/// <remarks>
/// This cache helps to avoid redundant validation checks for SSNs that have already been processed.
/// It maps the SSN as a string to a boolean indicating whether the SSN is valid (true) or not (false).
/// Utilizing this cache can significantly improve performance for applications that frequently validate the same SSNs.
/// This cache helps to avoid redundant validation checks for national identity numbers (nin) that have already been processed.
/// It maps the identity numbers as a string to a boolean indicating whether the number is valid (true) or not (false).
/// Utilizing this cache can significantly improve performance for applications that frequently validate the same numbers.
/// </remarks>
private static ConcurrentDictionary<string, bool> CachedSocialSecurityNumber => new();
private static ConcurrentDictionary<string, bool> CachedNationalIdentityNumber => new();
}
21 changes: 10 additions & 11 deletions src/Altinn.Profile.Core/Integrations/IUnitProfileRepository.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
using Altinn.Profile.Core.Unit.ContactPoints;

namespace Altinn.Profile.Core.Integrations
namespace Altinn.Profile.Core.Integrations;

/// <summary>
/// Interface for accessing user profile services related to unit contact points.
/// </summary>
public interface IUnitProfileRepository
{
/// <summary>
/// Interface describing a client for the user profile service
/// Retrieves a list of user-registered contact points based on the specified lookup criteria.
/// </summary>
public interface IUnitProfileRepository
{
/// <summary>
/// Provides a list of user registered contact points based on the lookup criteria
/// </summary>
/// <param name="lookup">Lookup object containing a list of organizations and a resource</param>
/// <returns>A list of unit contact points</returns>
Task<Result<UnitContactPointsList, bool>> GetUserRegisteredContactPoints(UnitContactPointLookup lookup);
}
/// <param name="lookup">An object containing a list of organization numbers and a resource ID to filter the contact points.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains a <see cref="Result{TValue, TError}"/> object with a <see cref="UnitContactPointsList"/> on success, or a boolean indicating failure.</returns>
Task<Result<UnitContactPointsList, bool>> GetUserRegisteredContactPoints(UnitContactPointLookup lookup);
}
Loading

0 comments on commit e75d02a

Please sign in to comment.