diff --git a/src/RealCV.Application/Interfaces/IProfessionalVerifierService.cs b/src/RealCV.Application/Interfaces/IProfessionalVerifierService.cs
index 7cb2c04..f499f99 100644
--- a/src/RealCV.Application/Interfaces/IProfessionalVerifierService.cs
+++ b/src/RealCV.Application/Interfaces/IProfessionalVerifierService.cs
@@ -3,7 +3,7 @@ using RealCV.Application.Models;
namespace RealCV.Application.Interfaces;
///
-/// Service for verifying professional qualifications (FCA, SRA, etc.)
+/// Service for verifying professional qualifications (FCA)
///
public interface IProfessionalVerifierService
{
@@ -15,23 +15,10 @@ public interface IProfessionalVerifierService
string? firmName = null,
string? referenceNumber = null);
- ///
- /// Verify if a person is a registered solicitor with the SRA
- ///
- Task VerifySolicitorAsync(
- string name,
- string? sraNumber = null,
- string? firmName = null);
-
///
/// Search FCA register for individuals
///
Task> SearchFcaIndividualsAsync(string name);
-
- ///
- /// Search SRA register for solicitors
- ///
- Task> SearchSolicitorsAsync(string name);
}
public sealed record FcaIndividualSearchResult
@@ -41,12 +28,3 @@ public sealed record FcaIndividualSearchResult
public string? Status { get; init; }
public List? CurrentFirms { get; init; }
}
-
-public sealed record SraSolicitorSearchResult
-{
- public required string Name { get; init; }
- public required string SraNumber { get; init; }
- public string? Status { get; init; }
- public string? CurrentOrganisation { get; init; }
- public string? AdmissionDate { get; init; }
-}
diff --git a/src/RealCV.Application/Models/ProfessionalVerificationResult.cs b/src/RealCV.Application/Models/ProfessionalVerificationResult.cs
index 252ace1..d98873e 100644
--- a/src/RealCV.Application/Models/ProfessionalVerificationResult.cs
+++ b/src/RealCV.Application/Models/ProfessionalVerificationResult.cs
@@ -1,7 +1,7 @@
namespace RealCV.Application.Models;
///
-/// Result of verifying a professional qualification (FCA, SRA, etc.)
+/// Result of verifying a professional qualification (FCA)
///
public sealed record ProfessionalVerificationResult
{
@@ -20,11 +20,6 @@ public sealed record ProfessionalVerificationResult
public List? ApprovedFunctions { get; init; }
public List? ControlledFunctions { get; init; }
- // For SRA
- public string? SolicitorType { get; init; }
- public string? AdmissionDate { get; init; }
- public string? PractisingCertificateStatus { get; init; }
-
public string? VerificationNotes { get; init; }
public List Flags { get; init; } = [];
}
diff --git a/src/RealCV.Infrastructure/Clients/SraRegisterClient.cs b/src/RealCV.Infrastructure/Clients/SraRegisterClient.cs
deleted file mode 100644
index 0e17d43..0000000
--- a/src/RealCV.Infrastructure/Clients/SraRegisterClient.cs
+++ /dev/null
@@ -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 _logger;
-
- public SraRegisterClient(
- HttpClient httpClient,
- ILogger logger)
- {
- _httpClient = httpClient;
- _logger = logger;
-
- _httpClient.BaseAddress = new Uri("https://sra-prod-apim.azure-api.net/");
- }
-
- public async Task 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(JsonOptions);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error searching SRA for solicitor: {Name}", name);
- return null;
- }
- }
-
- public async Task 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(JsonOptions);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error getting SRA solicitor: {SraNumber}", sraNumber);
- return null;
- }
- }
-
- public async Task 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(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? 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? PreviousPositions { get; set; }
- public List? 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? 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; }
-}
diff --git a/src/RealCV.Infrastructure/DependencyInjection.cs b/src/RealCV.Infrastructure/DependencyInjection.cs
index 1e109f0..05c4b53 100644
--- a/src/RealCV.Infrastructure/DependencyInjection.cs
+++ b/src/RealCV.Infrastructure/DependencyInjection.cs
@@ -100,10 +100,6 @@ public static class DependencyInjection
services.AddHttpClient()
.AddPolicyHandler(GetRetryPolicy());
- // Configure HttpClient for SRA Register API
- services.AddHttpClient()
- .AddPolicyHandler(GetRetryPolicy());
-
// Configure HttpClient for GitHub API
services.AddHttpClient()
.AddPolicyHandler(GetRetryPolicy());
diff --git a/src/RealCV.Infrastructure/Services/ProfessionalVerifierService.cs b/src/RealCV.Infrastructure/Services/ProfessionalVerifierService.cs
index 4d16928..98beeae 100644
--- a/src/RealCV.Infrastructure/Services/ProfessionalVerifierService.cs
+++ b/src/RealCV.Infrastructure/Services/ProfessionalVerifierService.cs
@@ -8,16 +8,13 @@ namespace RealCV.Infrastructure.Services;
public sealed class ProfessionalVerifierService : IProfessionalVerifierService
{
private readonly FcaRegisterClient _fcaClient;
- private readonly SraRegisterClient _sraClient;
private readonly ILogger _logger;
public ProfessionalVerifierService(
FcaRegisterClient fcaClient,
- SraRegisterClient sraClient,
ILogger logger)
{
_fcaClient = fcaClient;
- _sraClient = sraClient;
_logger = logger;
}
@@ -148,150 +145,6 @@ public sealed class ProfessionalVerifierService : IProfessionalVerifierService
}
}
- public async Task 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> SearchFcaIndividualsAsync(string name)
{
try
@@ -323,35 +176,6 @@ public sealed class ProfessionalVerifierService : IProfessionalVerifierService
}
}
- public async Task> 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))
diff --git a/src/RealCV.Web/appsettings.json b/src/RealCV.Web/appsettings.json
index 272bc79..cbca3a7 100644
--- a/src/RealCV.Web/appsettings.json
+++ b/src/RealCV.Web/appsettings.json
@@ -19,8 +19,8 @@
"ContainerName": "cv-uploads"
},
"FcaRegister": {
- "ApiKey": "",
- "Email": ""
+ "ApiKey": "9ae1aee51e5c717a1135775501c89075",
+ "Email": "peter.foster@ukdataservices.co.uk"
},
"GitHub": {
"PersonalAccessToken": ""
diff --git a/tools/ApiTester/ApiTester.csproj b/tools/ApiTester/ApiTester.csproj
new file mode 100644
index 0000000..91b464a
--- /dev/null
+++ b/tools/ApiTester/ApiTester.csproj
@@ -0,0 +1,10 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/tools/ApiTester/Program.cs b/tools/ApiTester/Program.cs
new file mode 100644
index 0000000..7ac6992
--- /dev/null
+++ b/tools/ApiTester/Program.cs
@@ -0,0 +1,123 @@
+using System.Net.Http.Headers;
+using System.Text.Json;
+
+Console.WriteLine("=== RealCV API Integration Tester ===\n");
+
+// Test 1: FCA Register API
+Console.WriteLine("1. Testing FCA Register API...");
+try
+{
+ var fcaClient = new HttpClient();
+ fcaClient.BaseAddress = new Uri("https://register.fca.org.uk/services/V0.1/");
+ fcaClient.DefaultRequestHeaders.Add("X-Auth-Email", "peter.foster@ukdataservices.co.uk");
+ fcaClient.DefaultRequestHeaders.Add("X-Auth-Key", "9ae1aee51e5c717a1135775501c89075");
+
+ var fcaResponse = await fcaClient.GetAsync("Individuals?q=John%20Smith&page=1");
+ Console.WriteLine($" Status: {fcaResponse.StatusCode}");
+
+ if (fcaResponse.IsSuccessStatusCode)
+ {
+ var content = await fcaResponse.Content.ReadAsStringAsync();
+ Console.WriteLine($" ✓ FCA API working");
+ using var doc = JsonDocument.Parse(content);
+ if (doc.RootElement.TryGetProperty("Data", out var data) && data.ValueKind == JsonValueKind.Array)
+ {
+ Console.WriteLine($" Found {data.GetArrayLength()} individuals matching 'John Smith'");
+ }
+ else
+ {
+ Console.WriteLine($" Response: {content.Substring(0, Math.Min(200, content.Length))}");
+ }
+ }
+ else
+ {
+ Console.WriteLine($" ✗ Error: {fcaResponse.StatusCode}");
+ }
+}
+catch (Exception ex)
+{
+ Console.WriteLine($" ✗ Error: {ex.Message}");
+}
+
+Console.WriteLine();
+
+// Test 2: ORCID API
+Console.WriteLine("2. Testing ORCID API...");
+try
+{
+ var orcidClient = new HttpClient();
+ orcidClient.BaseAddress = new Uri("https://pub.orcid.org/v3.0/");
+ orcidClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+
+ // Get a known ORCID record directly
+ var orcidResponse = await orcidClient.GetAsync("0000-0001-5109-3700/record");
+ Console.WriteLine($" Status: {orcidResponse.StatusCode}");
+
+ if (orcidResponse.IsSuccessStatusCode)
+ {
+ var content = await orcidResponse.Content.ReadAsStringAsync();
+ Console.WriteLine($" ✓ ORCID API working");
+ using var doc = JsonDocument.Parse(content);
+ if (doc.RootElement.TryGetProperty("person", out var person) &&
+ person.TryGetProperty("name", out var name))
+ {
+ var givenName = "";
+ var familyName = "";
+ if (name.TryGetProperty("given-names", out var gn) && gn.TryGetProperty("value", out var gnv))
+ givenName = gnv.GetString() ?? "";
+ if (name.TryGetProperty("family-name", out var fn) && fn.TryGetProperty("value", out var fnv))
+ familyName = fnv.GetString() ?? "";
+ Console.WriteLine($" Retrieved record for: {givenName} {familyName}");
+ }
+ }
+ else
+ {
+ Console.WriteLine($" ✗ Error: {orcidResponse.StatusCode}");
+ }
+}
+catch (Exception ex)
+{
+ Console.WriteLine($" ✗ Error: {ex.Message}");
+}
+
+Console.WriteLine();
+
+// Test 3: GitHub API
+Console.WriteLine("3. Testing GitHub API...");
+try
+{
+ var githubClient = new HttpClient();
+ githubClient.BaseAddress = new Uri("https://api.github.com/");
+ githubClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github+json"));
+ githubClient.DefaultRequestHeaders.Add("X-GitHub-Api-Version", "2022-11-28");
+ githubClient.DefaultRequestHeaders.UserAgent.ParseAdd("RealCV/1.0");
+
+ var githubResponse = await githubClient.GetAsync("users/torvalds");
+ Console.WriteLine($" Status: {githubResponse.StatusCode}");
+
+ if (githubResponse.IsSuccessStatusCode)
+ {
+ var content = await githubResponse.Content.ReadAsStringAsync();
+ Console.WriteLine($" ✓ GitHub API working");
+ using var doc = JsonDocument.Parse(content);
+ var name = doc.RootElement.GetProperty("name").GetString();
+ var repos = doc.RootElement.GetProperty("public_repos").GetInt32();
+ var followers = doc.RootElement.GetProperty("followers").GetInt32();
+ Console.WriteLine($" User: {name}, Repos: {repos}, Followers: {followers}");
+ }
+ else
+ {
+ Console.WriteLine($" ✗ Error: {githubResponse.StatusCode}");
+ }
+
+ if (githubResponse.Headers.TryGetValues("X-RateLimit-Remaining", out var remaining))
+ {
+ Console.WriteLine($" Rate limit remaining: {remaining.First()}");
+ }
+}
+catch (Exception ex)
+{
+ Console.WriteLine($" ✗ Error: {ex.Message}");
+}
+
+Console.WriteLine("\n=== Tests complete ===");