refactor: Remove SRA integration (no public API available)
The SRA (Solicitors Regulation Authority) does not provide a public REST API. Their register is only accessible via their website. Removed all SRA-related code and added ApiTester tool for testing remaining integrations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,181 +0,0 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace RealCV.Infrastructure.Clients;
|
||||
|
||||
public sealed class SraRegisterClient
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<SraRegisterClient> _logger;
|
||||
|
||||
public SraRegisterClient(
|
||||
HttpClient httpClient,
|
||||
ILogger<SraRegisterClient> logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
|
||||
_httpClient.BaseAddress = new Uri("https://sra-prod-apim.azure-api.net/");
|
||||
}
|
||||
|
||||
public async Task<SraSolicitorSearchResponse?> SearchSolicitorsAsync(string name, int page = 1, int pageSize = 20)
|
||||
{
|
||||
try
|
||||
{
|
||||
var encodedName = Uri.EscapeDataString(name);
|
||||
var url = $"solicitors/search?name={encodedName}&page={page}&pageSize={pageSize}";
|
||||
|
||||
_logger.LogDebug("Searching SRA for solicitor: {Name}", name);
|
||||
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogWarning("SRA API returned {StatusCode} for search: {Name}",
|
||||
response.StatusCode, name);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<SraSolicitorSearchResponse>(JsonOptions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error searching SRA for solicitor: {Name}", name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SraSolicitorDetails?> GetSolicitorAsync(string sraNumber)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = $"solicitors/{sraNumber}";
|
||||
|
||||
_logger.LogDebug("Getting SRA solicitor: {SraNumber}", sraNumber);
|
||||
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogWarning("SRA API returned {StatusCode} for SRA number: {SraNumber}",
|
||||
response.StatusCode, sraNumber);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<SraSolicitorDetails>(JsonOptions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting SRA solicitor: {SraNumber}", sraNumber);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SraOrganisationSearchResponse?> SearchOrganisationsAsync(string name, int page = 1, int pageSize = 20)
|
||||
{
|
||||
try
|
||||
{
|
||||
var encodedName = Uri.EscapeDataString(name);
|
||||
var url = $"organisations/search?name={encodedName}&page={page}&pageSize={pageSize}";
|
||||
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<SraOrganisationSearchResponse>(JsonOptions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error searching SRA for organisation: {Name}", name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
}
|
||||
|
||||
// Response models
|
||||
public class SraSolicitorSearchResponse
|
||||
{
|
||||
public List<SraSolicitorSearchItem>? Results { get; set; }
|
||||
public int TotalCount { get; set; }
|
||||
public int Page { get; set; }
|
||||
public int PageSize { get; set; }
|
||||
}
|
||||
|
||||
public class SraSolicitorSearchItem
|
||||
{
|
||||
public string? SraId { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public string? Town { get; set; }
|
||||
public string? AdmissionDate { get; set; }
|
||||
public string? CurrentOrganisation { get; set; }
|
||||
}
|
||||
|
||||
public class SraSolicitorDetails
|
||||
{
|
||||
public string? SraId { get; set; }
|
||||
public string? Forename { get; set; }
|
||||
public string? MiddleNames { get; set; }
|
||||
public string? Surname { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public string? AdmissionDate { get; set; }
|
||||
public string? SolicitorType { get; set; }
|
||||
public string? PractisingCertificateStatus { get; set; }
|
||||
|
||||
public SraCurrentPosition? CurrentPosition { get; set; }
|
||||
public List<SraPreviousPosition>? PreviousPositions { get; set; }
|
||||
public List<SraDisciplinaryRecord>? DisciplinaryHistory { get; set; }
|
||||
|
||||
public string FullName => string.Join(" ",
|
||||
new[] { Forename, MiddleNames, Surname }.Where(s => !string.IsNullOrWhiteSpace(s)));
|
||||
}
|
||||
|
||||
public class SraCurrentPosition
|
||||
{
|
||||
public string? OrganisationName { get; set; }
|
||||
public string? OrganisationSraId { get; set; }
|
||||
public string? Role { get; set; }
|
||||
public string? StartDate { get; set; }
|
||||
}
|
||||
|
||||
public class SraPreviousPosition
|
||||
{
|
||||
public string? OrganisationName { get; set; }
|
||||
public string? OrganisationSraId { get; set; }
|
||||
public string? Role { get; set; }
|
||||
public string? StartDate { get; set; }
|
||||
public string? EndDate { get; set; }
|
||||
}
|
||||
|
||||
public class SraDisciplinaryRecord
|
||||
{
|
||||
public string? DecisionDate { get; set; }
|
||||
public string? DecisionType { get; set; }
|
||||
public string? Summary { get; set; }
|
||||
}
|
||||
|
||||
public class SraOrganisationSearchResponse
|
||||
{
|
||||
public List<SraOrganisationSearchItem>? Results { get; set; }
|
||||
public int TotalCount { get; set; }
|
||||
}
|
||||
|
||||
public class SraOrganisationSearchItem
|
||||
{
|
||||
public string? SraId { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public string? Type { get; set; }
|
||||
public string? Town { get; set; }
|
||||
}
|
||||
@@ -100,10 +100,6 @@ public static class DependencyInjection
|
||||
services.AddHttpClient<FcaRegisterClient>()
|
||||
.AddPolicyHandler(GetRetryPolicy());
|
||||
|
||||
// Configure HttpClient for SRA Register API
|
||||
services.AddHttpClient<SraRegisterClient>()
|
||||
.AddPolicyHandler(GetRetryPolicy());
|
||||
|
||||
// Configure HttpClient for GitHub API
|
||||
services.AddHttpClient<GitHubApiClient>()
|
||||
.AddPolicyHandler(GetRetryPolicy());
|
||||
|
||||
@@ -8,16 +8,13 @@ namespace RealCV.Infrastructure.Services;
|
||||
public sealed class ProfessionalVerifierService : IProfessionalVerifierService
|
||||
{
|
||||
private readonly FcaRegisterClient _fcaClient;
|
||||
private readonly SraRegisterClient _sraClient;
|
||||
private readonly ILogger<ProfessionalVerifierService> _logger;
|
||||
|
||||
public ProfessionalVerifierService(
|
||||
FcaRegisterClient fcaClient,
|
||||
SraRegisterClient sraClient,
|
||||
ILogger<ProfessionalVerifierService> logger)
|
||||
{
|
||||
_fcaClient = fcaClient;
|
||||
_sraClient = sraClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -148,150 +145,6 @@ public sealed class ProfessionalVerifierService : IProfessionalVerifierService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ProfessionalVerificationResult> VerifySolicitorAsync(
|
||||
string name,
|
||||
string? sraNumber = null,
|
||||
string? firmName = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Verifying SRA registration for: {Name}", name);
|
||||
|
||||
// If we have an SRA number, try to get directly
|
||||
if (!string.IsNullOrEmpty(sraNumber))
|
||||
{
|
||||
var details = await _sraClient.GetSolicitorAsync(sraNumber);
|
||||
if (details != null)
|
||||
{
|
||||
var isNameMatch = IsNameMatch(name, details.FullName);
|
||||
|
||||
return new ProfessionalVerificationResult
|
||||
{
|
||||
ClaimedName = name,
|
||||
ProfessionalBody = "SRA",
|
||||
IsVerified = isNameMatch,
|
||||
RegistrationNumber = details.SraId,
|
||||
MatchedName = details.FullName,
|
||||
Status = details.Status,
|
||||
AdmissionDate = details.AdmissionDate,
|
||||
SolicitorType = details.SolicitorType,
|
||||
PractisingCertificateStatus = details.PractisingCertificateStatus,
|
||||
CurrentEmployer = details.CurrentPosition?.OrganisationName,
|
||||
VerificationNotes = isNameMatch
|
||||
? $"SRA ID: {details.SraId}"
|
||||
: "SRA number found but name does not match",
|
||||
Flags = details.DisciplinaryHistory?.Count > 0
|
||||
? [new ProfessionalVerificationFlag
|
||||
{
|
||||
Type = "DisciplinaryRecord",
|
||||
Severity = "Warning",
|
||||
Message = $"Has {details.DisciplinaryHistory.Count} disciplinary record(s)",
|
||||
ScoreImpact = -20
|
||||
}]
|
||||
: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Search by name
|
||||
var searchResponse = await _sraClient.SearchSolicitorsAsync(name);
|
||||
|
||||
if (searchResponse?.Results == null || searchResponse.Results.Count == 0)
|
||||
{
|
||||
return new ProfessionalVerificationResult
|
||||
{
|
||||
ClaimedName = name,
|
||||
ProfessionalBody = "SRA",
|
||||
IsVerified = false,
|
||||
VerificationNotes = "No matching SRA registered solicitors found"
|
||||
};
|
||||
}
|
||||
|
||||
// Find best match
|
||||
var matches = searchResponse.Results
|
||||
.Where(s => IsNameMatch(name, s.Name))
|
||||
.ToList();
|
||||
|
||||
if (matches.Count == 0)
|
||||
{
|
||||
return new ProfessionalVerificationResult
|
||||
{
|
||||
ClaimedName = name,
|
||||
ProfessionalBody = "SRA",
|
||||
IsVerified = false,
|
||||
VerificationNotes = $"Found {searchResponse.Results.Count} results but no close name matches"
|
||||
};
|
||||
}
|
||||
|
||||
// If firm specified, try to match on that too
|
||||
SraSolicitorSearchItem? bestMatch = null;
|
||||
if (!string.IsNullOrEmpty(firmName))
|
||||
{
|
||||
bestMatch = matches.FirstOrDefault(m =>
|
||||
m.CurrentOrganisation?.Contains(firmName, StringComparison.OrdinalIgnoreCase) == true);
|
||||
}
|
||||
|
||||
bestMatch ??= matches.First();
|
||||
|
||||
// Get detailed information
|
||||
if (!string.IsNullOrEmpty(bestMatch.SraId))
|
||||
{
|
||||
var details = await _sraClient.GetSolicitorAsync(bestMatch.SraId);
|
||||
if (details != null)
|
||||
{
|
||||
return new ProfessionalVerificationResult
|
||||
{
|
||||
ClaimedName = name,
|
||||
ProfessionalBody = "SRA",
|
||||
IsVerified = true,
|
||||
RegistrationNumber = details.SraId,
|
||||
MatchedName = details.FullName,
|
||||
Status = details.Status,
|
||||
AdmissionDate = details.AdmissionDate,
|
||||
SolicitorType = details.SolicitorType,
|
||||
PractisingCertificateStatus = details.PractisingCertificateStatus,
|
||||
CurrentEmployer = details.CurrentPosition?.OrganisationName,
|
||||
VerificationNotes = $"SRA ID: {details.SraId}",
|
||||
Flags = details.DisciplinaryHistory?.Count > 0
|
||||
? [new ProfessionalVerificationFlag
|
||||
{
|
||||
Type = "DisciplinaryRecord",
|
||||
Severity = "Warning",
|
||||
Message = $"Has {details.DisciplinaryHistory.Count} disciplinary record(s)",
|
||||
ScoreImpact = -20
|
||||
}]
|
||||
: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Basic verification without details
|
||||
return new ProfessionalVerificationResult
|
||||
{
|
||||
ClaimedName = name,
|
||||
ProfessionalBody = "SRA",
|
||||
IsVerified = true,
|
||||
RegistrationNumber = bestMatch.SraId,
|
||||
MatchedName = bestMatch.Name,
|
||||
Status = bestMatch.Status,
|
||||
AdmissionDate = bestMatch.AdmissionDate,
|
||||
CurrentEmployer = bestMatch.CurrentOrganisation,
|
||||
VerificationNotes = "Verified via SRA Register search"
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error verifying SRA registration for: {Name}", name);
|
||||
return new ProfessionalVerificationResult
|
||||
{
|
||||
ClaimedName = name,
|
||||
ProfessionalBody = "SRA",
|
||||
IsVerified = false,
|
||||
VerificationNotes = $"Error during verification: {ex.Message}"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<FcaIndividualSearchResult>> SearchFcaIndividualsAsync(string name)
|
||||
{
|
||||
try
|
||||
@@ -323,35 +176,6 @@ public sealed class ProfessionalVerifierService : IProfessionalVerifierService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<SraSolicitorSearchResult>> SearchSolicitorsAsync(string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
var searchResponse = await _sraClient.SearchSolicitorsAsync(name);
|
||||
|
||||
if (searchResponse?.Results == null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return searchResponse.Results
|
||||
.Select(s => new SraSolicitorSearchResult
|
||||
{
|
||||
Name = s.Name ?? "Unknown",
|
||||
SraNumber = s.SraId ?? "Unknown",
|
||||
Status = s.Status,
|
||||
CurrentOrganisation = s.CurrentOrganisation,
|
||||
AdmissionDate = s.AdmissionDate
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error searching SRA for: {Name}", name);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsNameMatch(string searchName, string? foundName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(foundName))
|
||||
|
||||
Reference in New Issue
Block a user