-
-
Notifications
You must be signed in to change notification settings - Fork 86
RG-T133 Customer bug fixes #482
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
|
|
@@ -174,42 +175,105 @@ public void ClearAllUserProfilesFromCache(int departmentId) | |
|
|
||
| public async Task<UserProfile> GetProfileByMobileNumberAsync(string number) | ||
| { | ||
| string numberToTest = | ||
| number.Replace(" ", "").Replace("(", "").Replace(")", "").Replace("+", "").Replace("-", "").Replace(".", "").Trim(); | ||
| return await FindProfileByPhoneAsync(number, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unhandled exception path in Core/Resgrid.Services/UserProfileService.cs at lines 185, 211, and this await site: return await FindProfileByPhoneAsync(number, ...) propagates repository lookup failures without contextual diagnostics. Wrap the await in try/catch, log the failure with the phone number, and then rethrow or map the exception to an application error. Kody rule violation: Handle async operations with proper error handling try
{
return await FindProfileByPhoneAsync(number,
_userProfileRepository.GetProfileByMobileNumberAsync,
profile => profile.MobileNumberVerified);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get profile by mobile number for {PhoneNumber}", number);
throw;
}Prompt for LLMTalk to Kody by mentioning @kody Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing structured error logging in Core/Resgrid.Services/UserProfileService.cs at lines 185, 211, and this await site obscures failures in FindProfileByPhoneAsync(number, ...) and removes operation and identifier context from diagnostics. Add a try/catch that logs the operation name and input phone number as structured fields before rethrowing. Kody rule violation: Include error context in structured logs try
{
return await FindProfileByPhoneAsync(number,
_userProfileRepository.GetProfileByMobileNumberAsync,
profile => profile.MobileNumberVerified);
}
catch (Exception ex)
{
_logger.LogError(ex, "GetProfileByMobileNumberAsync failed", new { operation = nameof(GetProfileByMobileNumberAsync), phoneNumber = number });
throw;
}Prompt for LLMTalk to Kody by mentioning @kody Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction. |
||
| _userProfileRepository.GetProfileByMobileNumberAsync, | ||
| profile => profile.MobileNumberVerified); | ||
| } | ||
|
|
||
| var profile = await _userProfileRepository.GetProfileByMobileNumberAsync(numberToTest); | ||
| public async Task<UserProfile> GetProfileByHomeNumberAsync(string number) | ||
| { | ||
| return await FindProfileByPhoneAsync(number, | ||
| _userProfileRepository.GetProfileByHomeNumberAsync, | ||
| profile => profile.HomeNumberVerified); | ||
| } | ||
|
|
||
| if (profile != null) | ||
| return profile; | ||
| /// <summary> | ||
| /// Resolves the profile that owns a phone number, preferring one that has actually proven it. | ||
| /// <para> | ||
| /// The same number can sit on more than one profile - a stale or secondary account, or someone | ||
| /// who mistyped it and never completed verification. A profile that verified the number is the | ||
| /// only one that has demonstrated possession, so it wins outright, even over a closer match on | ||
| /// the number's shape. Everything else falls back to candidate order (the number exactly as | ||
| /// dialled before its country-code variant). | ||
| /// </para> | ||
| /// <para> | ||
| /// Within a single candidate the query does the same ranking, so this only has to arbitrate | ||
| /// between candidates. | ||
| /// </para> | ||
| /// </summary> | ||
| private static async Task<UserProfile> FindProfileByPhoneAsync(string number, | ||
| Func<string, Task<UserProfile>> lookup, Func<UserProfile, bool?> isVerified) | ||
| { | ||
| UserProfile unverifiedMatch = null; | ||
|
|
||
| if (numberToTest.Length == 11 && numberToTest[0] == char.Parse("1")) | ||
| foreach (var candidate in PhoneLookupCandidates(number)) | ||
| { | ||
| numberToTest = numberToTest.Remove(0, 1); | ||
| var profile = await lookup(candidate); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unwrapped external call boundary in Core/Resgrid.Services/UserProfileService.cs: var profile = await lookup(candidate); can propagate raw infrastructure exceptions without candidate context. Wrap the lookup delegate invocation in try/catch, log the candidate, and rethrow or map the exception. Kody rule violation: Add try-catch blocks for external calls try
{
var profile = await lookup(candidate);
// existing logic
}
catch (Exception ex)
{
_logger.LogError(ex, "External lookup failed for {Candidate}", candidate);
throw;
}Prompt for LLMTalk to Kody by mentioning @kody Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction. |
||
|
|
||
| if (profile == null) | ||
| continue; | ||
|
|
||
| if (isVerified(profile) == true) | ||
| return profile; | ||
|
|
||
| return await _userProfileRepository.GetProfileByMobileNumberAsync(numberToTest); | ||
| // Keep the first one found so a candidate that matches nothing verified still resolves, | ||
| // but keep looking in case a later candidate did verify the number. | ||
| unverifiedMatch ??= profile; | ||
| } | ||
|
|
||
| return null; | ||
| return unverifiedMatch; | ||
| } | ||
|
|
||
| public async Task<UserProfile> GetProfileByHomeNumberAsync(string number) | ||
| /// <summary> | ||
| /// The stored numbers a lookup should be tried against, most-specific first. | ||
| /// <para> | ||
| /// Profiles are saved in E.164 (+12015550123) while inbound SMS and voice hand us the number in | ||
| /// whatever shape the carrier used, so a lookup has to cover the country code being present on | ||
| /// one side but not the other. The leading "+" is covered by the query itself, which matches the | ||
| /// stored value both bare and plus-prefixed. | ||
| /// </para> | ||
| /// <para> | ||
| /// The order matters and the candidates are tried one at a time rather than matched together: | ||
| /// 2015550123 and 12015550123 can be two different profiles, and the repository takes | ||
| /// FirstOrDefault() with no ORDER BY. Asking for the number exactly as dialled first means the | ||
| /// country-code variant is only ever reached as a fallback. | ||
| /// </para> | ||
| /// </summary> | ||
| private static IEnumerable<string> PhoneLookupCandidates(string number) | ||
| { | ||
| string numberToTest = | ||
| number.Replace(" ", "").Replace("(", "").Replace(")", "").Replace("+", "").Replace("-", "").Replace(".", "").Trim(); | ||
| var digits = NormalizePhoneNumber(number); | ||
|
|
||
| var profile = await _userProfileRepository.GetProfileByMobileNumberAsync(numberToTest); | ||
| // A blank inbound number must never match: the stored column can also be blank and an | ||
| // empty-to-empty compare would hand back an arbitrary profile. | ||
| if (string.IsNullOrWhiteSpace(digits)) | ||
| yield break; | ||
|
|
||
| if (profile != null) | ||
| return profile; | ||
| yield return digits; | ||
|
|
||
| if (numberToTest.Length == 11 && numberToTest[0] == char.Parse("1")) | ||
| { | ||
| numberToTest = numberToTest.Remove(0, 1); | ||
| if (digits.Length == 11 && digits[0] == '1') | ||
| yield return digits.Substring(1); | ||
| else if (digits.Length == 10) | ||
| yield return "1" + digits; | ||
| } | ||
|
|
||
| return await _userProfileRepository.GetProfileByMobileNumberAsync(numberToTest); | ||
| /// <summary> | ||
| /// Reduces a number to bare digits. Inbound numbers arrive formatted in assorted ways | ||
| /// ("+1 (201) 555-0123"), and only the digits are comparable against a stored number. | ||
| /// </summary> | ||
| private static string NormalizePhoneNumber(string number) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(number)) | ||
| return null; | ||
|
|
||
| var digits = new StringBuilder(number.Length); | ||
|
|
||
| foreach (var character in number) | ||
| { | ||
| if (character >= '0' && character <= '9') | ||
| digits.Append(character); | ||
| } | ||
|
|
||
| return null; | ||
| return digits.ToString(); | ||
| } | ||
|
|
||
| public async Task<List<UserProfile>> GetSelectedUserProfilesAsync(List<string> userIds) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Invalid gateway address formatting in
Core/Resgrid.Services/SmsService.cs:ResolveGatewayNumberalways returnsPhoneNumberResult.LocalNumberfor carrier-gateway sends, but several carriers inCarriersMaprequire country-code digits in the mailbox address. Select the gateway format per carrier and preserveprocessed.InternationalNumberwithout the leading+for carriers such asRogersWirelessand the UK gateway carriers.Prompt for LLM
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.