-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Retrieve contact information for multiple users (#209)
* Correct name of container and solution folder * Fix comment typos * Use pattern matching for type check * Refactoring GetUserListByUuid to better fit it with the other methods * Correct typo in comment * Refactoring to arrow function for simplification * Cleaning up namespace for Core's ServiceCollectionExtensions * Add suffix "Service" to IUnitContactPoints * Correct namespace ..User.ContactPoints -> Unit.ContactPoints for IUnitContactPointsService * Simplify list initialization * Generate database models * Move the database context class to its own folder * Update the ContextClassName attribute * Implemented an endpoint to retrieve user contact information efficiently. * Organize each component into its appropriate namespace * Remove the unused dependencies and improve the service collection extension methods * Implement a simple validation logic * Specify the expected request and response content types * Code refactoring to achieve a clean separation of concerns, enhance flexibility, and improve the maintainability and testability * Code refactoring * Keep the validation in the service layer and use the AutoMapper to move mapping logic out of service. * Add a number of unit tests * Improve the unit tests * Code refactoring * Add a validation rule to validate national security numbers * Rearrange core extensions * Organize the matched SSN and the unmatched SSN in a respone object * Implement validation layer and refactor existing code. * Implement a use case for controllers to access and retrieve users’ contact details * Add unit tests for string extension methods * Code refactoring * Remove old unit tests * Fix typos * Fix a conflict * Rewrite a number of unit tests * Improve a comment * Update the internal endpoint address * Shorten the error message * Remove the parameter name argument. * Remove two unused variables * Changed the Register name to Person * Add the default parameter value defined in the overridden method. * Remove the nameof parameter * Remove an unnecessary check for null * Rename the service using the table name * Improve code smell * Code refactoring * Implement two unit tests to validate the functionality of the user contact details retrieval use case. * Create a new Migration folder and attach the database creation script * Regenerate the data models * Move the Person class to the entities folder * Change the table name from Register to Person * Rename a number of classes * Rename some classes * Rename more classes * Code refactoring * Remove an unnecessary check for null * Remove an unnecessary check for null * Add a simple unit test to test the public endpoint * Add a number of unit tests to test the public endpoint * Inject a logger to record information whenever an exception occurs * Rename the unit tests * Check the error message * Implement a single unit test to ensure that the controller does not block unnecessarily by mocking long-running tasks. * Implement unit tests to verify the functionality of the class responsible for validating national identity numbers * Implement unit tests to test retrieval of contact details * Update the internal endpoint to produce the same result as the external one * Fix a typo * Add three new unit tests * Copy some unit tests from the external controller to the internal one * Change the national identity number * Try to avoid duplicates * Check model validity * Implement two unit tests to verify the outcome when the data model is invalid. * Rename the unit tests * Update the unit tests to check the returned values * Increase the unit tests to cover valid and invalid national identity numbers, retrieving contact details, handling empty results, and managing repository exceptions gracefully in the PersonService * Use different types * Fix an issue introduced in the previous commit * Add the Reservation flag to the test data * Renamed a file * Add a unit test to test retrieving contact details * Code refactoring * Code refactoring * Add a missing dot * Rename a folder * Code refactoring * Cover more use cases * Increase covered use cases * Follow the same naming pattern * Implement a number of simple unit test to test the mapping profile * Delete the DROP DATABASE IF EXISTS statement * Delete an index that could be used to search for person data by a supplier identifier * Add statements to grant two users, we have, access to the schema * Remove the database creation statement * Regenerate the Person data model * Move the data models to the integration project * Fix connection string --------- Co-authored-by: Hallgeir Garnes-Gutvik <hallgeir.garnes-gutvik@digdir.no> Co-authored-by: Terje Holene <terje.holene@gmail.com>
- Loading branch information
1 parent
b9182bb
commit 7683f19
Showing
44 changed files
with
2,897 additions
and
104 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
#nullable enable | ||
|
||
namespace Altinn.Profile.Core.Domain; | ||
|
||
/// <summary> | ||
/// Defines generic methods for handling entities in a repository. | ||
/// </summary> | ||
/// <typeparam name="T">The type of the entity.</typeparam> | ||
public interface IRepository<T> | ||
where T : class | ||
{ | ||
/// <summary> | ||
/// Asynchronously retrieves all entities. | ||
/// </summary> | ||
/// <returns>A task that represents the asynchronous operation. The task result contains a collection of entities.</returns> | ||
Task<IEnumerable<T>> GetAllAsync(); | ||
|
||
/// <summary> | ||
/// Asynchronously retrieves entities with optional filtering, sorting, and pagination. | ||
/// </summary> | ||
/// <param name="filter">Optional filter criteria.</param> | ||
/// <param name="orderBy">Optional ordering criteria.</param> | ||
/// <param name="skip">The number of entities to skip.</param> | ||
/// <param name="take">The number of entities to take.</param> | ||
/// <returns>A task that represents the asynchronous operation. The task result contains a collection of filtered, sorted, and paginated entities.</returns> | ||
Task<IEnumerable<T>> GetAsync( | ||
Func<T, bool>? filter = null, | ||
Func<IEnumerable<T>, IOrderedEnumerable<T>>? orderBy = null, | ||
int? skip = null, | ||
int? take = null); | ||
|
||
/// <summary> | ||
/// Asynchronously retrieves an entity by its identifier. | ||
/// </summary> | ||
/// <param name="id">The identifier of the entity.</param> | ||
/// <returns>A task that represents the asynchronous operation. The task result contains the entity that matches the identifier.</returns> | ||
Task<T?> GetByIdAsync(string id); | ||
|
||
/// <summary> | ||
/// Asynchronously checks if an entity exists by its identifier. | ||
/// </summary> | ||
/// <param name="id">The identifier of the entity.</param> | ||
/// <returns>A task that represents the asynchronous operation. The task result contains a boolean indicating the existence of the entity.</returns> | ||
Task<bool> ExistsAsync(string id); | ||
|
||
/// <summary> | ||
/// Asynchronously adds a new entity. | ||
/// </summary> | ||
/// <param name="entity">The entity to add.</param> | ||
/// <returns>A task that represents the asynchronous operation. The task result contains the added entity.</returns> | ||
Task<T> AddAsync(T entity); | ||
|
||
/// <summary> | ||
/// Asynchronously adds multiple entities. | ||
/// </summary> | ||
/// <param name="entities">The entities to add.</param> | ||
/// <returns>A task that represents the asynchronous operation.</returns> | ||
Task AddRangeAsync(IEnumerable<T> entities); | ||
|
||
/// <summary> | ||
/// Asynchronously updates an existing entity. | ||
/// </summary> | ||
/// <param name="entity">The entity to update.</param> | ||
/// <returns>A task that represents the asynchronous operation.</returns> | ||
Task UpdateAsync(T entity); | ||
|
||
/// <summary> | ||
/// Asynchronously updates multiple entities. | ||
/// </summary> | ||
/// <param name="entities">The entities to update.</param> | ||
/// <returns>A task that represents the asynchronous operation.</returns> | ||
Task UpdateRangeAsync(IEnumerable<T> entities); | ||
|
||
/// <summary> | ||
/// Asynchronously deletes an entity by its identifier. | ||
/// </summary> | ||
/// <param name="id">The identifier of the entity to delete.</param> | ||
/// <returns>A task that represents the asynchronous operation.</returns> | ||
Task DeleteAsync(string id); | ||
|
||
/// <summary> | ||
/// Asynchronously deletes multiple entities by their identifiers. | ||
/// </summary> | ||
/// <param name="ids">The identifiers of the entities to delete.</param> | ||
/// <returns>A task that represents the asynchronous operation.</returns> | ||
Task DeleteRangeAsync(IEnumerable<string> ids); | ||
|
||
/// <summary> | ||
/// Saves changes to the data source asynchronously. | ||
/// </summary> | ||
/// <returns>A task that represents the asynchronous save operation. The task result contains the number of state entries written to the data source.</returns> | ||
Task<int> SaveChangesAsync(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
using System.Collections.Concurrent; | ||
using System.Globalization; | ||
using System.Text.RegularExpressions; | ||
|
||
namespace Altinn.Profile.Core.Extensions; | ||
|
||
/// <summary> | ||
/// Extension class for <see cref="string"/> to add more members. | ||
/// </summary> | ||
public static partial class StringExtensions | ||
{ | ||
/// <summary> | ||
/// Determines whether a given string consists of only digits. | ||
/// </summary> | ||
/// <param name="input">The string to check.</param> | ||
/// <returns> | ||
/// <c>true</c> if the given string consists of only digits; otherwise, <c>false</c>. | ||
/// </returns> | ||
/// <remarks> | ||
/// This method checks if the provided string is not null or whitespace and matches the regex pattern for digits. | ||
/// The regex pattern ensures that the string contains only numeric characters (0-9). | ||
/// </remarks> | ||
public static bool IsDigitsOnly(this string input) | ||
{ | ||
if (string.IsNullOrWhiteSpace(input)) | ||
{ | ||
return false; | ||
} | ||
|
||
return DigitsOnlyRegex().IsMatch(input); | ||
} | ||
|
||
/// <summary> | ||
/// Removes all whitespace characters from the given string. | ||
/// </summary> | ||
/// <param name="stringToClean">The string from which to remove whitespace characters.</param> | ||
/// <returns> | ||
/// A new string with all whitespace characters removed. | ||
/// If the input is null, empty, or consists only of whitespace, the original input is returned. | ||
/// </returns> | ||
public static string? RemoveWhitespace(this string stringToClean) | ||
{ | ||
if (string.IsNullOrWhiteSpace(stringToClean)) | ||
{ | ||
return stringToClean?.Trim(); | ||
} | ||
|
||
return WhitespaceRegex().Replace(stringToClean, string.Empty); | ||
} | ||
|
||
/// <summary> | ||
/// Determines whether a given string represents a valid format for a Norwegian Social Security Number (SSN). | ||
/// </summary> | ||
/// <param name="socialSecurityNumber">The Norwegian Social Security Number (SSN) 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>. | ||
/// </returns> | ||
/// <remarks> | ||
/// A valid Norwegian Social Security Number (SSN) 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="ArgumentOutOfRangeException">Thrown when the parsed date is outside the range of DateTime.</exception> | ||
public static bool IsValidSocialSecurityNumber(this string socialSecurityNumber, bool controlDigits = true) | ||
{ | ||
if (string.IsNullOrWhiteSpace(socialSecurityNumber) || socialSecurityNumber.Length != 11) | ||
{ | ||
return false; | ||
} | ||
|
||
// Return the cached result if the given string has been checked once. | ||
if (CachedSocialSecurityNumber.TryGetValue(socialSecurityNumber, out var cachedResult)) | ||
{ | ||
return cachedResult; | ||
} | ||
|
||
ReadOnlySpan<char> socialSecurityNumberSpan = socialSecurityNumber.AsSpan(); | ||
|
||
for (int i = 0; i < socialSecurityNumberSpan.Length; i++) | ||
{ | ||
if (!char.IsDigit(socialSecurityNumberSpan[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]; | ||
|
||
// If parsing the individual number part fails, return false. | ||
if (!int.TryParse(individualNumberPart, out _)) | ||
{ | ||
return false; | ||
} | ||
|
||
// Validate the date part. | ||
if (!DateTime.TryParseExact(datePart.ToString(), "ddMMyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out _)) | ||
{ | ||
return false; | ||
} | ||
|
||
var isValidSocialSecurityNumber = !controlDigits || CalculateControlDigits(socialSecurityNumberSpan[..9].ToString()) == controlDigitsPart.ToString(); | ||
|
||
CachedSocialSecurityNumber.TryAdd(socialSecurityNumber, isValidSocialSecurityNumber); | ||
|
||
return isValidSocialSecurityNumber; | ||
} | ||
|
||
/// <summary> | ||
/// Calculates the control digits used to validate a Norwegian Social Security Number. | ||
/// </summary> | ||
/// <param name="firstNineDigits">The first nine digits of the Social Security Number.</param> | ||
/// <returns>A <see cref="string"/> represents the two control digits.</returns> | ||
private static string CalculateControlDigits(string firstNineDigits) | ||
{ | ||
int[] weightsFirst = [3, 7, 6, 1, 8, 9, 4, 5, 2]; | ||
|
||
int[] weightsSecond = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]; | ||
|
||
int firstControlDigit = CalculateControlDigit(firstNineDigits, weightsFirst); | ||
|
||
int secondControlDigit = CalculateControlDigit(firstNineDigits + firstControlDigit, weightsSecond); | ||
|
||
return $"{firstControlDigit}{secondControlDigit}"; | ||
} | ||
|
||
/// <summary> | ||
/// Calculates a control digit using the specified weights. | ||
/// </summary> | ||
/// <param name="digits">The digits to use in the calculation.</param> | ||
/// <param name="weights">The weights for each digit.</param> | ||
/// <returns>An <see cref="int"/> represents the calculated control digit.</returns> | ||
private static int CalculateControlDigit(string digits, int[] weights) | ||
{ | ||
int sum = 0; | ||
|
||
for (int i = 0; i < weights.Length; i++) | ||
{ | ||
sum += (int)char.GetNumericValue(digits[i]) * weights[i]; | ||
} | ||
|
||
int remainder = sum % 11; | ||
return remainder == 0 ? 0 : 11 - remainder; | ||
} | ||
|
||
/// <summary> | ||
/// Generates a compiled regular expression for matching all whitespace characters in a string. | ||
/// </summary> | ||
/// <returns> | ||
/// A <see cref="Regex"/> object that can be used to match all whitespace characters in a string. | ||
/// </returns> | ||
[GeneratedRegex(@"\s+", RegexOptions.Compiled)] | ||
private static partial Regex WhitespaceRegex(); | ||
|
||
/// <summary> | ||
/// Generates a compiled regular expression for validating that a string consists of only digits. | ||
/// </summary> | ||
/// <returns> | ||
/// A <see cref="Regex"/> object that can be used to validate that a string contains only digits. | ||
/// </returns> | ||
[GeneratedRegex(@"^\d+$", RegexOptions.Compiled)] | ||
private static partial Regex DigitsOnlyRegex(); | ||
|
||
/// <summary> | ||
/// A cache for storing the validation results of Norwegian Social Security Numbers (SSNs). | ||
/// </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. | ||
/// </remarks> | ||
private static ConcurrentDictionary<string, bool> CachedSocialSecurityNumber => new(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
src/Altinn.Profile.Integrations/Entities/IPersonContactDetails.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
#nullable enable | ||
|
||
namespace Altinn.Profile.Integrations.Entities; | ||
|
||
/// <summary> | ||
/// Represents a person's contact details. | ||
/// </summary> | ||
public interface IPersonContactDetails | ||
{ | ||
/// <summary> | ||
/// Gets the national identity number of the person. | ||
/// </summary> | ||
string NationalIdentityNumber { get; } | ||
|
||
/// <summary> | ||
/// Gets a value indicating whether the person opts out of being contacted. | ||
/// </summary> | ||
bool? IsReserved { get; } | ||
|
||
/// <summary> | ||
/// Gets the mobile phone number of the person. | ||
/// </summary> | ||
string? MobilePhoneNumber { get; } | ||
|
||
/// <summary> | ||
/// Gets the email address of the person. | ||
/// </summary> | ||
string? EmailAddress { get; } | ||
|
||
/// <summary> | ||
/// Gets the language code of the person, represented as an ISO 639-1 code. | ||
/// </summary> | ||
string? LanguageCode { get; } | ||
} |
27 changes: 27 additions & 0 deletions
27
src/Altinn.Profile.Integrations/Entities/IPersonContactDetailsLookupResult.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#nullable enable | ||
|
||
using System.Collections.Immutable; | ||
|
||
namespace Altinn.Profile.Integrations.Entities; | ||
|
||
/// <summary> | ||
/// Represents the result of a lookup operation for contact details. | ||
/// </summary> | ||
public interface IPersonContactDetailsLookupResult | ||
{ | ||
/// <summary> | ||
/// Gets a list of national identity numbers that could not be matched with any person contact details. | ||
/// </summary> | ||
/// <value> | ||
/// An <see cref="ImmutableList{T}"/> of <see cref="string"/> containing the unmatched national identity numbers. | ||
/// </value> | ||
ImmutableList<string>? UnmatchedNationalIdentityNumbers { get; } | ||
|
||
/// <summary> | ||
/// Gets a list of person contact details that were successfully matched during the lookup. | ||
/// </summary> | ||
/// <value> | ||
/// An <see cref="ImmutableList{T}"/> of <see cref="IPersonContactDetails"/> containing the matched person contact details. | ||
/// </value> | ||
ImmutableList<IPersonContactDetails>? MatchedPersonContactDetails { get; } | ||
} |
4 changes: 1 addition & 3 deletions
4
src/Altinn.Profile/Models/MailboxSupplier.cs → ....Integrations/Entities/MailboxSupplier.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.