refactor: Remove redundant code and consolidate JsonSerializerOptions

- Remove unused GetRepoLanguagesAsync method from GitHubClient
- Remove unused IsFakeAccreditor and FakeAccreditors from DiplomaMills
- Remove unused CompanyVerificationFlagPenalty constant from ProcessCVCheckJob
- Remove unused SkillVerification properties (TotalLinesOfCode, FirstUsed, LastUsed)
- Remove unused CompanyMatchRequest record from SemanticMatchResult
- Add JsonDefaults.ApiClient and consolidate duplicate JsonSerializerOptions across API clients
- Remove ApiTester tool containing hardcoded API credentials (security fix)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-25 04:16:51 +00:00
parent f775164212
commit a132efd907
10 changed files with 24 additions and 226 deletions

View File

@@ -135,24 +135,6 @@ public static class DiplomaMills
"distance learning university", // be careful - some are legit
];
/// <summary>
/// Fake accreditation bodies used by diploma mills.
/// </summary>
public static readonly HashSet<string> FakeAccreditors = new(StringComparer.OrdinalIgnoreCase)
{
"World Association of Universities and Colleges",
"WAUC",
"International Accreditation Agency",
"Universal Accreditation Council",
"Board of Online Universities Accreditation",
"International Council for Open and Distance Education",
"World Online Education Accrediting Commission",
"Central States Consortium of Colleges and Schools",
"American Council of Private Colleges and Universities",
"Association of Distance Learning Programs",
"International Distance Education Certification Agency",
};
/// <summary>
/// Check if an institution is a known diploma mill.
/// </summary>
@@ -196,15 +178,4 @@ public static class DiplomaMills
return false;
}
/// <summary>
/// Check if an accreditor is known to be fake.
/// </summary>
public static bool IsFakeAccreditor(string accreditorName)
{
if (string.IsNullOrWhiteSpace(accreditorName))
return false;
return FakeAccreditors.Contains(accreditorName.Trim());
}
}

View File

@@ -1,4 +1,5 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace RealCV.Application.Helpers;
@@ -16,4 +17,13 @@ public static class JsonDefaults
PropertyNameCaseInsensitive = true,
WriteIndented = true
};
/// <summary>
/// Options for consuming external APIs - case insensitive with null handling.
/// </summary>
public static readonly JsonSerializerOptions ApiClient = new()
{
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
}

View File

@@ -37,9 +37,6 @@ public sealed record SkillVerification
public required string ClaimedSkill { get; init; }
public required bool IsVerified { get; init; }
public int RepoCount { get; init; }
public int TotalLinesOfCode { get; init; }
public DateOnly? FirstUsed { get; init; }
public DateOnly? LastUsed { get; init; }
public string? Notes { get; init; }
}

View File

@@ -10,12 +10,6 @@ public record SemanticMatchResult
public bool IsMatch => ConfidenceScore >= 70;
}
public record CompanyMatchRequest
{
public required string CVCompanyName { get; init; }
public required List<CompanyCandidate> Candidates { get; init; }
}
public record CompanyCandidate
{
public required string CompanyName { get; init; }

View File

@@ -1,8 +1,8 @@
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using RealCV.Application.Helpers;
namespace RealCV.Infrastructure.Clients;
@@ -44,7 +44,7 @@ public sealed class FcaRegisterClient
return null;
}
return await response.Content.ReadFromJsonAsync<FcaIndividualResponse>(JsonOptions);
return await response.Content.ReadFromJsonAsync<FcaIndividualResponse>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
@@ -70,7 +70,7 @@ public sealed class FcaRegisterClient
return null;
}
var wrapper = await response.Content.ReadFromJsonAsync<FcaIndividualDetailsWrapper>(JsonOptions);
var wrapper = await response.Content.ReadFromJsonAsync<FcaIndividualDetailsWrapper>(JsonDefaults.ApiClient);
return wrapper?.Data?.FirstOrDefault();
}
catch (Exception ex)
@@ -94,7 +94,7 @@ public sealed class FcaRegisterClient
return null;
}
return await response.Content.ReadFromJsonAsync<FcaFirmResponse>(JsonOptions);
return await response.Content.ReadFromJsonAsync<FcaFirmResponse>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
@@ -102,12 +102,6 @@ public sealed class FcaRegisterClient
return null;
}
}
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
}
public class FcaOptions

View File

@@ -1,9 +1,9 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using RealCV.Application.Helpers;
namespace RealCV.Infrastructure.Clients;
@@ -49,7 +49,7 @@ public sealed class GitHubApiClient
return null;
}
return await response.Content.ReadFromJsonAsync<GitHubUser>(JsonOptions);
return await response.Content.ReadFromJsonAsync<GitHubUser>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
@@ -76,7 +76,7 @@ public sealed class GitHubApiClient
break;
}
var pageRepos = await response.Content.ReadFromJsonAsync<List<GitHubRepo>>(JsonOptions);
var pageRepos = await response.Content.ReadFromJsonAsync<List<GitHubRepo>>(JsonDefaults.ApiClient);
if (pageRepos == null || pageRepos.Count == 0)
{
@@ -107,28 +107,6 @@ public sealed class GitHubApiClient
return repos;
}
public async Task<Dictionary<string, int>?> GetRepoLanguagesAsync(string owner, string repo)
{
try
{
var url = $"repos/{Uri.EscapeDataString(owner)}/{Uri.EscapeDataString(repo)}/languages";
var response = await _httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
return null;
}
return await response.Content.ReadFromJsonAsync<Dictionary<string, int>>(JsonOptions);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting languages for repo: {Owner}/{Repo}", owner, repo);
return null;
}
}
public async Task<GitHubUserSearchResponse?> SearchUsersAsync(string query, int perPage = 30)
{
try
@@ -143,7 +121,7 @@ public sealed class GitHubApiClient
return null;
}
return await response.Content.ReadFromJsonAsync<GitHubUserSearchResponse>(JsonOptions);
return await response.Content.ReadFromJsonAsync<GitHubUserSearchResponse>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
@@ -151,12 +129,6 @@ public sealed class GitHubApiClient
return null;
}
}
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
}
public class GitHubOptions

View File

@@ -1,8 +1,8 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using RealCV.Application.Helpers;
namespace RealCV.Infrastructure.Clients;
@@ -40,7 +40,7 @@ public sealed class OrcidClient
return null;
}
return await response.Content.ReadFromJsonAsync<OrcidSearchResponse>(JsonOptions);
return await response.Content.ReadFromJsonAsync<OrcidSearchResponse>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
@@ -69,7 +69,7 @@ public sealed class OrcidClient
return null;
}
return await response.Content.ReadFromJsonAsync<OrcidRecord>(JsonOptions);
return await response.Content.ReadFromJsonAsync<OrcidRecord>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
@@ -92,7 +92,7 @@ public sealed class OrcidClient
return null;
}
return await response.Content.ReadFromJsonAsync<OrcidWorks>(JsonOptions);
return await response.Content.ReadFromJsonAsync<OrcidWorks>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
@@ -115,7 +115,7 @@ public sealed class OrcidClient
return null;
}
return await response.Content.ReadFromJsonAsync<OrcidEmployments>(JsonOptions);
return await response.Content.ReadFromJsonAsync<OrcidEmployments>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
@@ -138,7 +138,7 @@ public sealed class OrcidClient
return null;
}
return await response.Content.ReadFromJsonAsync<OrcidEducations>(JsonOptions);
return await response.Content.ReadFromJsonAsync<OrcidEducations>(JsonDefaults.ApiClient);
}
catch (Exception ex)
{
@@ -155,12 +155,6 @@ public sealed class OrcidClient
.Trim();
return orcidId;
}
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
}
// Response models

View File

@@ -24,7 +24,6 @@ public sealed class ProcessCVCheckJob
private const int BaseScore = 100;
private const int UnverifiedCompanyPenalty = 10;
private const int ImplausibleJobTitlePenalty = 15;
private const int CompanyVerificationFlagPenalty = 5; // Base penalty for company flags, actual from flag.ScoreImpact
private const int RapidProgressionPenalty = 10;
private const int EarlyCareerSeniorRolePenalty = 10;
private const int GapMonthPenalty = 1;

View File

@@ -1,10 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -1,123 +0,0 @@
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 ===");