using System; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using PcgsInvUi.Models; using System.Text.Json; using ReactiveUI; namespace PcgsInvUi.Services; public class PcgsClient : ReactiveObject { public enum ErrorType { None, NoCoinFound, InvalidRequestFormat, ApiKeyInvalid } public int NumApiRequests { get => _numApiRequests; set => this.RaiseAndSetIfChanged(ref _numApiRequests, value); } private string _apiUri = "https://api.pcgs.com/publicapi"; private readonly string _apiKey; private int _numApiRequests; /// /// Creates a new instance of the PcgsClient class. /// Although this is a constructor, it's meant to be called only from the CreateClient method. /// private PcgsClient(string apiKey) { _apiKey = apiKey; } /// /// Creates a new instance of the PcgsClient class. /// /// The api key to use for requests. /// A new instance of the PcgsClient class, or null if the key is invalid. public static async Task CreateClient(string apiKey) { // Check if the api key is valid. var testClient = new PcgsClient(apiKey); var validCreds = await testClient.VerifyCredentials(); if (!validCreds) return null; return testClient; } /// /// Sends a request to the API to verify the credentials. /// /// A boolean indicating whether or not the credentials are valid. public async Task VerifyCredentials() { // Create a way to check if the api key given is valid // I think the best way to do this is to make a request to the api and see if it returns the expected response. var result = await this.GetCoinFactsByGrade(1, "AG-03"); // This is a known invalid coin id - this is because the api is very fast with invalid requests. if (result.Item1 == ErrorType.ApiKeyInvalid) return false; return true; } /// /// Gets the coin information for a given PCGS number and grade. /// /// The PCGS number of the coin. /// The grade of the coin. /// Whether or not the coin is a plus grade. /// A tuple containing the error type and the coin information. public async Task<(ErrorType, Coin?)> GetCoinFactsByGrade(int pcgsNumber, string grade, bool plusGrade = false) { using (HttpClient client = new HttpClient()) { var gradeParam = GetGradeFromString(grade); string requestUri = $"{_apiUri}/coindetail/GetCoinFactsByGrade/?PCGSNo={pcgsNumber}&GradeNo={gradeParam}&PlusGrade={plusGrade}"; client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", _apiKey); Console.WriteLine($"Sending request to {requestUri}"); HttpResponseMessage response = await client.GetAsync(requestUri); Console.WriteLine("Got response."); if (response.IsSuccessStatusCode) { // Check response for errors. string jsonResponse = await response.Content.ReadAsStringAsync(); var parsedResponse = ParseJsonResponse(jsonResponse); return parsedResponse; } } return (ErrorType.ApiKeyInvalid, null); } /// /// Utility function to parse the grade number from the grade string passed from the front end. /// /// The grade string. /// The grade number. private int GetGradeFromString(string grade) { var actualGrade = int.Parse(grade.Substring(grade.Length - 2)); // Throws an exception if the grade doesn't exist. Console.WriteLine($"Grade is {actualGrade}"); return actualGrade; } /// /// Parses the json response from the api. /// /// The json response. /// A tuple containing an error type and the coin information. private (ErrorType, Coin?) ParseJsonResponse(string json) { Console.WriteLine(json); // Check for errors in the response if (json.Contains("\"ServerMessage\":\"No data found\"")) return (ErrorType.NoCoinFound, null); if (json.Contains("\"IsValidRequest\":false")) return (ErrorType.InvalidRequestFormat, null); var newCoin = JsonSerializer.Deserialize(json); return (ErrorType.None, newCoin); } } // Exception for invalid request format class InvalidRequestFormatException : Exception { public InvalidRequestFormatException() { } public InvalidRequestFormatException(string message) : base(message) { } public InvalidRequestFormatException(string message, Exception inner) : base(message, inner) { } } // Exception for no coin found class CoinNotFoundException : Exception { public CoinNotFoundException() { } public CoinNotFoundException(string message) : base(message) { } public CoinNotFoundException(string message, Exception inner) : base(message, inner) { } }