Add API key management. #10
No files matched your search
@@ -17,11 +17,10 @@ public partial class App : Application {
|
|||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
|
||||||
var coins = new CoinDatabase();
|
var coins = new CoinDatabase();
|
||||||
desktop.MainWindow = new MainWindow {
|
desktop.MainWindow = new MainWindow {
|
||||||
DataContext = new MainWindowViewModel(coins, !coins.TryInitApiClient()),
|
DataContext = new MainWindowViewModel(coins),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Allow the application to save the database when it exits.
|
// 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) => {
|
desktop.Exit += (sender, args) => {
|
||||||
coins.UpdateCollection("CollectionTable");
|
coins.UpdateCollection("CollectionTable");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ public class CoinDatabase {
|
|||||||
Console.WriteLine("Got collection.");
|
Console.WriteLine("Got collection.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Destructor. Currently not used, due to the strange nature of desktop applications.
|
||||||
|
/// </summary>
|
||||||
~CoinDatabase() {
|
~CoinDatabase() {
|
||||||
Console.WriteLine("Destructor called, saving collection.");
|
Console.WriteLine("Destructor called, saving collection.");
|
||||||
// Save collection.
|
// Save collection.
|
||||||
@@ -42,7 +45,7 @@ public class CoinDatabase {
|
|||||||
/// Attempts to intitialize the API client by selecting the API key from the database.
|
/// Attempts to intitialize the API client by selecting the API key from the database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>True if the API key was found, false otherwise.</returns>
|
/// <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... ");
|
Console.Write("Attempting to initialize API client... ");
|
||||||
SQLiteCommand cmd = Connection.CreateCommand();
|
SQLiteCommand cmd = Connection.CreateCommand();
|
||||||
cmd.CommandText = "SELECT API_KEY FROM ApiTable";
|
cmd.CommandText = "SELECT API_KEY FROM ApiTable";
|
||||||
@@ -50,7 +53,7 @@ public class CoinDatabase {
|
|||||||
using (var reader = cmd.ExecuteReader()) {
|
using (var reader = cmd.ExecuteReader()) {
|
||||||
if (reader.Read()) {
|
if (reader.Read()) {
|
||||||
var apiKey = reader.GetString(0);
|
var apiKey = reader.GetString(0);
|
||||||
_pcgsClient = PcgsClient.CreateClient(apiKey);
|
_pcgsClient = await PcgsClient.CreateClient(apiKey);
|
||||||
|
|
||||||
// Check that the client is valid.
|
// Check that the client is valid.
|
||||||
if (_pcgsClient is null) {
|
if (_pcgsClient is null) {
|
||||||
@@ -59,14 +62,8 @@ public class CoinDatabase {
|
|||||||
return IsClientConnected;
|
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;
|
IsClientConnected = true;
|
||||||
Console.WriteLine("Done!");
|
Console.WriteLine("Done! API key was found from database.");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
IsClientConnected = false;
|
IsClientConnected = false;
|
||||||
@@ -76,9 +73,13 @@ public class CoinDatabase {
|
|||||||
return IsClientConnected;
|
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... ");
|
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.
|
// TODO Check if the API key is valid and if it is, then add to the database.
|
||||||
if (_pcgsClient is null) {
|
if (_pcgsClient is null) {
|
||||||
IsClientConnected = false;
|
IsClientConnected = false;
|
||||||
@@ -87,13 +88,19 @@ public class CoinDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var cmd = Connection.CreateCommand();
|
var cmd = Connection.CreateCommand();
|
||||||
cmd.CommandText = $"INSERT INTO ApiTable (API_KEY, REQUESTS_REMAINING) VALUES ({apiKey}, 1000)";
|
cmd.CommandText = $"INSERT INTO ApiTable (API_KEY, REQUESTS_REMAINING) VALUES (\"{apiKey}\", 1000)";
|
||||||
// cmd.ExecuteNonQuery();
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
IsClientConnected = true;
|
IsClientConnected = true;
|
||||||
Console.WriteLine("Done!");
|
Console.WriteLine("Done! API key was added to database.");
|
||||||
return IsClientConnected;
|
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() {
|
private SQLiteConnection CreateConnection() {
|
||||||
// Check if database exists.
|
// Check if database exists.
|
||||||
if (!System.IO.File.Exists("coin_collection")) {
|
if (!System.IO.File.Exists("coin_collection")) {
|
||||||
@@ -119,6 +126,9 @@ public class CoinDatabase {
|
|||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the table that stores the API key and the number of requests remaining.
|
||||||
|
/// </summary>
|
||||||
public void CreateApiTable() {
|
public void CreateApiTable() {
|
||||||
// Create a table to store the API key and the number of requests remaining.
|
// Create a table to store the API key and the number of requests remaining.
|
||||||
if (!IsConnected) {
|
if (!IsConnected) {
|
||||||
@@ -176,6 +186,9 @@ public class CoinDatabase {
|
|||||||
Console.WriteLine("Collection table initialized.");
|
Console.WriteLine("Collection table initialized.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a Coin object into the database.
|
||||||
|
/// </summary>
|
||||||
public void InsertCoin(Coin coin) {
|
public void InsertCoin(Coin coin) {
|
||||||
Console.WriteLine("Inserting coin...");
|
Console.WriteLine("Inserting coin...");
|
||||||
// Use our existing connection to insert a coin to the database.
|
// Use our existing connection to insert a coin to the database.
|
||||||
@@ -202,6 +215,13 @@ public class CoinDatabase {
|
|||||||
Console.WriteLine("Coin inserted.");
|
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) {
|
public async Task<PcgsClient.ErrorType> CreateCoin(int pcgsNumber, string grade, int quantity) {
|
||||||
Console.WriteLine("Creating coin...");
|
Console.WriteLine("Creating coin...");
|
||||||
// Create a coin by calling the API, then insert it into the database.
|
// Create a coin by calling the API, then insert it into the database.
|
||||||
@@ -227,6 +247,12 @@ public class CoinDatabase {
|
|||||||
return apiResult.Item1;
|
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) {
|
public Coin? GetCoin(int pcgsNumber, string grade) {
|
||||||
Console.WriteLine($"Getting coin {pcgsNumber}, {grade}...");
|
Console.WriteLine($"Getting coin {pcgsNumber}, {grade}...");
|
||||||
int gradeInt = GetGradeFromString(grade);
|
int gradeInt = GetGradeFromString(grade);
|
||||||
@@ -246,6 +272,10 @@ public class CoinDatabase {
|
|||||||
return null;
|
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) {
|
public void GetCollection(string collectionName) {
|
||||||
Console.WriteLine("Getting collection...");
|
Console.WriteLine("Getting collection...");
|
||||||
SQLiteCommand cmd = Connection.CreateCommand();
|
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) {
|
public String CollectionToCSV(string collectionName) {
|
||||||
var cmd = Connection.CreateCommand();
|
var cmd = Connection.CreateCommand();
|
||||||
cmd.CommandText = $"SELECT * FROM {collectionName}";
|
cmd.CommandText = $"SELECT * FROM {collectionName}";
|
||||||
@@ -307,6 +342,11 @@ public class CoinDatabase {
|
|||||||
return resultString;
|
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) {
|
public void SaveCoin(string collectionName, Coin coin) {
|
||||||
// Get and replace the coin from the database.
|
// Get and replace the coin from the database.
|
||||||
Console.WriteLine("Saving coin...");
|
Console.WriteLine("Saving coin...");
|
||||||
@@ -333,6 +373,10 @@ public class CoinDatabase {
|
|||||||
cmd.ExecuteNonQuery();
|
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) {
|
public void UpdateCollection(string collectionName) {
|
||||||
// Update the database with the contents of the collection.
|
// 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.
|
// 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) {
|
private Coin CoinFromReader(SQLiteDataReader reader) {
|
||||||
// Create a coin while filtering out any possible null values - they throw exceptions.
|
// Create a coin while filtering out any possible null values - they throw exceptions.
|
||||||
Coin coin = new Coin();
|
Coin coin = new Coin();
|
||||||
@@ -364,6 +412,10 @@ public class CoinDatabase {
|
|||||||
return coin;
|
return coin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a Coin object from the database.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delCoin">The coin to delete.</param>
|
||||||
public void DeleteCoin(Coin delCoin) {
|
public void DeleteCoin(Coin delCoin) {
|
||||||
SQLiteCommand cmd = Connection.CreateCommand();
|
SQLiteCommand cmd = Connection.CreateCommand();
|
||||||
cmd.CommandText = "DELETE FROM CollectionTable WHERE PCGS_NUMBER = @PcgsNumber AND GRADE = @Grade";
|
cmd.CommandText = "DELETE FROM CollectionTable WHERE PCGS_NUMBER = @PcgsNumber AND GRADE = @Grade";
|
||||||
@@ -373,6 +425,11 @@ public class CoinDatabase {
|
|||||||
cmd.ExecuteNonQuery();
|
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) {
|
private int GetGradeFromString(string grade) {
|
||||||
var actualGrade = int.Parse(grade.Substring(grade.Length - 2));
|
var actualGrade = int.Parse(grade.Substring(grade.Length - 2));
|
||||||
Console.WriteLine($"Grade is {actualGrade}");
|
Console.WriteLine($"Grade is {actualGrade}");
|
||||||
|
|||||||
@@ -39,18 +39,19 @@ public class PcgsClient : ReactiveObject {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="apiKey">The api key to use for requests.</param>
|
/// <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>
|
/// <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.
|
// Check if the api key is valid.
|
||||||
var testClient = new PcgsClient(apiKey);
|
var testClient = new PcgsClient(apiKey);
|
||||||
if (!testClient.VerifyCredentials()) return null;
|
var validCreds = await testClient.VerifyCredentials();
|
||||||
|
if (!validCreds) return null;
|
||||||
return testClient;
|
return testClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool VerifyCredentials() {
|
public async Task<bool> VerifyCredentials() {
|
||||||
// Create a way to check if the api key given is valid
|
// 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.
|
// 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.
|
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.Result.Item1 == ErrorType.ApiKeyInvalid) return false;
|
if (result.Item1 == ErrorType.ApiKeyInvalid) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +67,7 @@ public class PcgsClient : ReactiveObject {
|
|||||||
Console.WriteLine("Got response.");
|
Console.WriteLine("Got response.");
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode) {
|
if (response.IsSuccessStatusCode) {
|
||||||
|
// Check response for errors.
|
||||||
string jsonResponse = await response.Content.ReadAsStringAsync();
|
string jsonResponse = await response.Content.ReadAsStringAsync();
|
||||||
var parsedResponse = ParseJsonResponse(jsonResponse);
|
var parsedResponse = ParseJsonResponse(jsonResponse);
|
||||||
return parsedResponse;
|
return parsedResponse;
|
||||||
@@ -76,13 +78,11 @@ public class PcgsClient : ReactiveObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int GetGradeFromString(string grade) {
|
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}");
|
Console.WriteLine($"Grade is {actualGrade}");
|
||||||
return actualGrade;
|
return actualGrade;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Will eventually create the other methods that are included in the Public API.
|
|
||||||
|
|
||||||
private (ErrorType, Coin?) ParseJsonResponse(string json) {
|
private (ErrorType, Coin?) ParseJsonResponse(string json) {
|
||||||
Console.WriteLine(json);
|
Console.WriteLine(json);
|
||||||
// TODO Check for errors in the response
|
// TODO Check for errors in the response
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Reactive.Linq;
|
|||||||
using PcgsInvUi.Models;
|
using PcgsInvUi.Models;
|
||||||
using PcgsInvUi.Services;
|
using PcgsInvUi.Services;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace PcgsInvUi.ViewModels;
|
namespace PcgsInvUi.ViewModels;
|
||||||
|
|
||||||
@@ -35,9 +36,11 @@ public class MainWindowViewModel : ViewModelBase {
|
|||||||
private ViewModelBase _sideContent;
|
private ViewModelBase _sideContent;
|
||||||
private Coin? _selectedCoin;
|
private Coin? _selectedCoin;
|
||||||
private double _totalValue;
|
private double _totalValue;
|
||||||
|
|
||||||
public MainWindowViewModel(CoinDatabase coins, bool askForApiKey) {
|
public MainWindowViewModel(CoinDatabase coins) {
|
||||||
ConnectedCoinDatabase = coins;
|
ConnectedCoinDatabase = coins;
|
||||||
|
var keyFound = Task.Run(() => coins.TryInitApiClient()).Result;
|
||||||
|
Console.WriteLine("Function completed.");
|
||||||
var newViewModel = new NewViewModel();
|
var newViewModel = new NewViewModel();
|
||||||
var apiKeyViewModel = new ApiKeyViewModel();
|
var apiKeyViewModel = new ApiKeyViewModel();
|
||||||
// DisplayedList = CoinCollection = new ObservableCollection<Coin>(coins.GetItems());
|
// DisplayedList = CoinCollection = new ObservableCollection<Coin>(coins.GetItems());
|
||||||
@@ -45,13 +48,13 @@ public class MainWindowViewModel : ViewModelBase {
|
|||||||
TotalValue = DisplayedList.Sum(x => x.TotalPrice);
|
TotalValue = DisplayedList.Sum(x => x.TotalPrice);
|
||||||
|
|
||||||
// Setup interaction to show ApiKeyWindow
|
// Setup interaction to show ApiKeyWindow
|
||||||
apiKeyViewModel.OkCommand.Subscribe(apiKey => {
|
apiKeyViewModel.OkCommand.Subscribe(async apiKey => {
|
||||||
if (ConnectedCoinDatabase.TryInitApiClient(apiKey))
|
if (await ConnectedCoinDatabase.TryInitApiClient(apiKey))
|
||||||
SidebarContent = newViewModel; // Must assign this to the observable property.
|
SidebarContent = newViewModel; // Must assign this to the observable property rather than private variable.
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if API key must be entered.
|
// Check if API key must be entered.
|
||||||
if (askForApiKey) {
|
if (!keyFound) {
|
||||||
Console.WriteLine("API key must be entered by user.");
|
Console.WriteLine("API key must be entered by user.");
|
||||||
// Show the API key input view.
|
// Show the API key input view.
|
||||||
_sideContent = apiKeyViewModel;
|
_sideContent = apiKeyViewModel;
|
||||||
|
|||||||
Reference in new issue
Block a user