Added comments to CoinCollection code, and finished a basic framework for API key entry.

This commit is contained in:
alphafetus committed 2023-12-01 23:15:27 -05:00
1 parent 683a2cf87b
commit ba04d31c3e
4 files changed
+88 -29

No files matched your search

+1 -2
View File
@@ -17,11 +17,10 @@ public partial class App : Application {
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
var coins = new CoinDatabase();
desktop.MainWindow = new MainWindow {
DataContext = new MainWindowViewModel(coins, !coins.TryInitApiClient()),
DataContext = new MainWindowViewModel(coins),
};
// Allow the application to save the database when it exits.
// The issue is that this will break if the database is not initialized here.
desktop.Exit += (sender, args) => {
coins.UpdateCollection("CollectionTable");
};
+71 -14
View File
@@ -30,6 +30,9 @@ public class CoinDatabase {
Console.WriteLine("Got collection.");
}
/// <summary>
/// Destructor. Currently not used, due to the strange nature of desktop applications.
/// </summary>
~CoinDatabase() {
Console.WriteLine("Destructor called, saving collection.");
// Save collection.
@@ -42,7 +45,7 @@ public class CoinDatabase {
/// Attempts to intitialize the API client by selecting the API key from the database.
/// </summary>
/// <returns>True if the API key was found, false otherwise.</returns>
public bool TryInitApiClient() {
public async Task<bool> TryInitApiClient() {
Console.Write("Attempting to initialize API client... ");
SQLiteCommand cmd = Connection.CreateCommand();
cmd.CommandText = "SELECT API_KEY FROM ApiTable";
@@ -50,7 +53,7 @@ public class CoinDatabase {
using (var reader = cmd.ExecuteReader()) {
if (reader.Read()) {
var apiKey = reader.GetString(0);
_pcgsClient = PcgsClient.CreateClient(apiKey);
_pcgsClient = await PcgsClient.CreateClient(apiKey);
// Check that the client is valid.
if (_pcgsClient is null) {
@@ -59,14 +62,8 @@ public class CoinDatabase {
return IsClientConnected;
}
// This only runs if the key is valid. Insert the key into the database.
// TODO Create checks to ensure that the database doesn't already contain a key.
var insertCmd = Connection.CreateCommand();
insertCmd.CommandText = $"INSERT INTO ApiTable (API_KEY, REQUESTS_REMAINING) VALUE ({apiKey}, 1000)";
insertCmd.ExecuteNonQuery();
IsClientConnected = true;
Console.WriteLine("Done!");
Console.WriteLine("Done! API key was found from database.");
}
else {
IsClientConnected = false;
@@ -76,9 +73,13 @@ public class CoinDatabase {
return IsClientConnected;
}
public bool TryInitApiClient(string apiKey) {
/// <summary>
/// Atttempts to initialize the PCGS API client.
/// </summary>
/// <returns>A Task with a return value that returns true if the client intializes properly.</returns>
public async Task<bool> TryInitApiClient(string apiKey) {
Console.Write("Attempting to initialize API client with key param... ");
_pcgsClient = PcgsClient.CreateClient(apiKey);
_pcgsClient = await PcgsClient.CreateClient(apiKey);
// TODO Check if the API key is valid and if it is, then add to the database.
if (_pcgsClient is null) {
IsClientConnected = false;
@@ -87,13 +88,19 @@ public class CoinDatabase {
}
var cmd = Connection.CreateCommand();
cmd.CommandText = $"INSERT INTO ApiTable (API_KEY, REQUESTS_REMAINING) VALUES ({apiKey}, 1000)";
// cmd.ExecuteNonQuery();
cmd.CommandText = $"INSERT INTO ApiTable (API_KEY, REQUESTS_REMAINING) VALUES (\"{apiKey}\", 1000)";
await cmd.ExecuteNonQueryAsync();
IsClientConnected = true;
Console.WriteLine("Done!");
Console.WriteLine("Done! API key was added to database.");
return IsClientConnected;
}
/// <summary>
/// Gets the collection from the database and stores it in the Collection property.
/// Creates the collection table if it does not exist already.
/// </summary>
/// <returns>The SQLiteConnection that corresponds to the connected database.</returns>
private SQLiteConnection CreateConnection() {
// Check if database exists.
if (!System.IO.File.Exists("coin_collection")) {
@@ -119,6 +126,9 @@ public class CoinDatabase {
return connection;
}
/// <summary>
/// Creates the table that stores the API key and the number of requests remaining.
/// </summary>
public void CreateApiTable() {
// Create a table to store the API key and the number of requests remaining.
if (!IsConnected) {
@@ -176,6 +186,9 @@ public class CoinDatabase {
Console.WriteLine("Collection table initialized.");
}
/// <summary>
/// Inserts a Coin object into the database.
/// </summary>
public void InsertCoin(Coin coin) {
Console.WriteLine("Inserting coin...");
// Use our existing connection to insert a coin to the database.
@@ -202,6 +215,13 @@ public class CoinDatabase {
Console.WriteLine("Coin inserted.");
}
/// <summary>
/// Creates a Coin object and saves it to the database.
/// </summary>
/// <param name="pcgsNumber">The PCGS number of the coin.</param>
/// <param name="grade">The grade of the coin.</param>
/// <param name="quantity">The quantity of the coin.</param>
/// <returns>The error type returned by the API.</returns>
public async Task<PcgsClient.ErrorType> CreateCoin(int pcgsNumber, string grade, int quantity) {
Console.WriteLine("Creating coin...");
// Create a coin by calling the API, then insert it into the database.
@@ -227,6 +247,12 @@ public class CoinDatabase {
return apiResult.Item1;
}
/// <summary>
/// Gets a coin from the database.
/// </summary>
/// <param name="pcgsNumber">The PCGS number of the coin.</param>
/// <param name="grade">The grade of the coin.</param>
/// <returns>The coin if it exists, null otherwise.</returns>
public Coin? GetCoin(int pcgsNumber, string grade) {
Console.WriteLine($"Getting coin {pcgsNumber}, {grade}...");
int gradeInt = GetGradeFromString(grade);
@@ -246,6 +272,10 @@ public class CoinDatabase {
return null;
}
/// <summary>
/// Used to get all Coin objects within the database.
/// </summary>
/// <param name="collectionName">The name of the collection table.</param>
public void GetCollection(string collectionName) {
Console.WriteLine("Getting collection...");
SQLiteCommand cmd = Connection.CreateCommand();
@@ -259,6 +289,11 @@ public class CoinDatabase {
}
}
/// <summary>
/// Creates a CSV formatted string from a collection.
/// </summary>
/// <param name="collectionName">The name of the collection table.</param>
/// <returns>A CSV formatted string.</returns>
public String CollectionToCSV(string collectionName) {
var cmd = Connection.CreateCommand();
cmd.CommandText = $"SELECT * FROM {collectionName}";
@@ -307,6 +342,11 @@ public class CoinDatabase {
return resultString;
}
/// <summary>
/// Saves a Coin object to the database.
/// </summary>
/// <param name="collectionName">The name of the collection table.</param>
/// <param name="coin">The coin to save.</param>
public void SaveCoin(string collectionName, Coin coin) {
// Get and replace the coin from the database.
Console.WriteLine("Saving coin...");
@@ -333,6 +373,10 @@ public class CoinDatabase {
cmd.ExecuteNonQuery();
}
/// <summary>
/// Synchronizes the database with the current contents of the specified collection.
/// </summary>
/// <param name="collectionName">The name of the collection table.</param>
public void UpdateCollection(string collectionName) {
// Update the database with the contents of the collection.
// TODO This is a very inefficient way of doing this. I should be able to do this in one command.
@@ -341,6 +385,10 @@ public class CoinDatabase {
}
}
/// <summary>
/// Creates a Coin from the information in a reader specifically from this database.
/// </summary>
/// <param name="reader">The reader to get the information from.</param>
private Coin CoinFromReader(SQLiteDataReader reader) {
// Create a coin while filtering out any possible null values - they throw exceptions.
Coin coin = new Coin();
@@ -364,6 +412,10 @@ public class CoinDatabase {
return coin;
}
/// <summary>
/// Deletes a Coin object from the database.
/// </summary>
/// <param name="delCoin">The coin to delete.</param>
public void DeleteCoin(Coin delCoin) {
SQLiteCommand cmd = Connection.CreateCommand();
cmd.CommandText = "DELETE FROM CollectionTable WHERE PCGS_NUMBER = @PcgsNumber AND GRADE = @Grade";
@@ -373,6 +425,11 @@ public class CoinDatabase {
cmd.ExecuteNonQuery();
}
/// <summary>
/// Gets the grade from a string returned from the main window.
/// These are always in a format similar to "MS-65", so we can just get the last two characters.
/// </summary>
/// <param name="grade">The grade string.</param>
private int GetGradeFromString(string grade) {
var actualGrade = int.Parse(grade.Substring(grade.Length - 2));
Console.WriteLine($"Grade is {actualGrade}");
+8 -8
View File
@@ -39,18 +39,19 @@ public class PcgsClient : ReactiveObject {
/// </summary>
/// <param name="apiKey">The api key to use for requests.</param>
/// <returns>A new instance of the PcgsClient class, or null if the key is invalid.</returns>
public static PcgsClient? CreateClient(string apiKey) {
public static async Task<PcgsClient?> CreateClient(string apiKey) {
// Check if the api key is valid.
var testClient = new PcgsClient(apiKey);
if (!testClient.VerifyCredentials()) return null;
var validCreds = await testClient.VerifyCredentials();
if (!validCreds) return null;
return testClient;
}
public bool VerifyCredentials() {
public async Task<bool> 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 = this.GetCoinFactsByGrade(1, "1"); // This is a known invalid coin id - this is because the api is very fast with invalid requests.
if (result.Result.Item1 == ErrorType.ApiKeyInvalid) return false;
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;
}
@@ -66,6 +67,7 @@ public class PcgsClient : ReactiveObject {
Console.WriteLine("Got response.");
if (response.IsSuccessStatusCode) {
// Check response for errors.
string jsonResponse = await response.Content.ReadAsStringAsync();
var parsedResponse = ParseJsonResponse(jsonResponse);
return parsedResponse;
@@ -76,13 +78,11 @@ public class PcgsClient : ReactiveObject {
}
private int GetGradeFromString(string grade) {
var actualGrade = int.Parse(grade.Substring(grade.Length - 2));
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;
}
// TODO Will eventually create the other methods that are included in the Public API.
private (ErrorType, Coin?) ParseJsonResponse(string json) {
Console.WriteLine(json);
// TODO Check for errors in the response
+8 -5
View File
@@ -7,6 +7,7 @@ using System.Reactive.Linq;
using PcgsInvUi.Models;
using PcgsInvUi.Services;
using ReactiveUI;
using System.Threading.Tasks;
namespace PcgsInvUi.ViewModels;
@@ -36,8 +37,10 @@ public class MainWindowViewModel : ViewModelBase {
private Coin? _selectedCoin;
private double _totalValue;
public MainWindowViewModel(CoinDatabase coins, bool askForApiKey) {
public MainWindowViewModel(CoinDatabase coins) {
ConnectedCoinDatabase = coins;
var keyFound = Task.Run(() => coins.TryInitApiClient()).Result;
Console.WriteLine("Function completed.");
var newViewModel = new NewViewModel();
var apiKeyViewModel = new ApiKeyViewModel();
// DisplayedList = CoinCollection = new ObservableCollection<Coin>(coins.GetItems());
@@ -45,13 +48,13 @@ public class MainWindowViewModel : ViewModelBase {
TotalValue = DisplayedList.Sum(x => x.TotalPrice);
// Setup interaction to show ApiKeyWindow
apiKeyViewModel.OkCommand.Subscribe(apiKey => {
if (ConnectedCoinDatabase.TryInitApiClient(apiKey))
SidebarContent = newViewModel; // Must assign this to the observable property.
apiKeyViewModel.OkCommand.Subscribe(async apiKey => {
if (await ConnectedCoinDatabase.TryInitApiClient(apiKey))
SidebarContent = newViewModel; // Must assign this to the observable property rather than private variable.
});
// Check if API key must be entered.
if (askForApiKey) {
if (!keyFound) {
Console.WriteLine("API key must be entered by user.");
// Show the API key input view.
_sideContent = apiKeyViewModel;