Files
RealCV/src/RealCV.Infrastructure/Clients/SraRegisterClient.cs
Peter Foster 5d2965beae feat: Add additional verification APIs (FCA, SRA, GitHub, OpenCorporates, ORCID)
This adds five new free API integrations for enhanced CV verification:

- FCA Register API: Verify financial services professionals
- SRA Register API: Verify solicitors and legal professionals
- GitHub API: Verify developer profiles and technical skills
- OpenCorporates API: Verify international companies across jurisdictions
- ORCID API: Verify academic researchers and publications

Includes:
- API clients for all five services with retry policies
- Service implementations with name matching and validation
- Models for verification results with detailed flags
- Configuration options in appsettings.json
- DI registration for all services

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 13:05:52 +00:00

182 lines
5.5 KiB
C#

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; }
}