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:
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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 ===");
|
||||
Reference in New Issue
Block a user