Implement user login and registration; update database connection management to be per use not a singleton by adjusting service lifetimes in DI.

This commit is contained in:
2026-02-26 18:45:15 -08:00
parent 6172d1c373
commit 79ee297e61
6 changed files with 194 additions and 90 deletions
+81 -21
View File
@@ -3,25 +3,43 @@
<PageTitle>Home</PageTitle>
<div class="card-holder">
<a class="card">
HOME
</a>
<a class="card">
S&P Chart
</a>
<a class="card">
Connect
</a>
<a class="card">
About
</a>
</div>
<div class="main-area">
<!-- Login Frame -->
@if (Session == null){
<div class="gridFrame">
<div><h2>LOGIN</h2></div>
<div class="loginRow">
<div><span>username</span></div>
<input @bind-value="userName" type="text" />
</div>
<div class="loginRow">
<div><span>password</span></div>
<input @bind-value="passWord" type="password" />
</div>
<div class="loginRow">
<button @onclick="LoginSession">Login</button>
<button @onclick="registerSession">Register</button>
</div>
<div>
<span style="color: red;">@loginError</span>
</div>
</div>
<!-- User Frame -->
}else{
<div class="gridFrame">
<span>UserName: @Session.UserName</span><br />
</div>
}
<!-- AI Frame -->
<div class="gridFrame">
<div>
<button @onclick="pullandtrain">@button1Text</button>
<button @onclick="predict">@button2Text</button>
</div>
<div>
@foreach (stockPredictionPair cur in predictions){
<div>
<h1>Symbol: @cur.Symbol</h1><br />
@@ -34,15 +52,21 @@
}
</div>
}
</div>
</div>
</div>
@code {
string button1Text = "Train AI";
string button2Text = "Predict AI";
// User Stuff
loginSession? Session = null;
// Login Stuff
string userName = "";
string passWord = "";
string loginError = "";
List<stockPredictionPair> predictions = new List<stockPredictionPair>(){
new stockPredictionPair(){ Symbol = "AAPL" },
@@ -51,6 +75,36 @@
new stockPredictionPair(){ Symbol = "FUN" }
};
async Task LoginSession(){
string dbPrefix = $"[{userName.ToLower()}]:"; // Set the DB prefix for the get and set
string passwordhash = dbDriver.Get( dbPrefix + "password" ); // Pull the password hash
if (BCrypt.Verify( passWord, passwordhash )){ // If the password is valid
Session = new loginSession(){
UserName = userName.ToLower()
};
}else{
loginError = "wrong password";
}
}
async Task registerSession(){
string dbPrefix = $"[{userName.ToLower()}]:";
string passwordhash = dbDriver.Get( dbPrefix + "password" );
if (string.IsNullOrEmpty(passwordhash)){
dbDriver.Set( dbPrefix + "password", BCrypt.HashPassword( passWord, BCrypt.GenerateSalt() ) );
Session = new loginSession(){
UserName = userName.ToLower()
};
}else{
loginError = "account is taken";
}
}
// AI Stuff
string button1Text = "Train AI";
string button2Text = "Predict AI";
async Task pullandtrain(){
button1Text = "Do not refresh the page. The data is refreshing.";
await Task.Delay(1);
@@ -76,9 +130,15 @@
await Task.Delay(1);
}
class stockPredictionPair{
public string Symbol = "";
public int Movement = 0;
// Data Types
class stockPredictionPair {
public string Symbol { get; set; } = "";
public int Movement { get; set; } = 0;
}
class loginSession {
public string UserName { get; set; } = "";
}
}
+60 -23
View File
@@ -1,25 +1,62 @@
.card-holder {
display: flex;
float: left;
flex-direction: column;
background-color: red;
width: 300px;
height: calc(100vh - 3.5rem);
}
.card {
background-color: blue;
margin: 5px;
height: 45px;
color: white;
align-items: center;
display: flex;
justify-content: center;
}
.main-area {
float: left;
background-color: aqua;
height: calc(100vh - 3.5rem);
width: calc(100vw - 300px);
display: grid;
gap: 20px;
padding: 20px;
grid-auto-columns: auto;
grid-auto-flow: column;
grid-template-columns: max-content;
overflow: scroll;
}
.gridFrame {
background: #eee;
border-radius: 12px;
padding: 1.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: 0.5s ease-in-out;
height: fit-content;
display: grid;
grid-row-gap: 10px;
border: 1px solid black;
}
.gridFrame h2 {
text-align: center;
margin: 0;
padding: 0;
border: 0;
}
.gridFrame:hover {
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
}
.loginRow {
width: 300px;
height: 40px;
padding-bottom: 10px;
}
.loginRow input {
width: calc(100% - 6px);
border: 1px solid black;
padding: 1px 2px;
}
.loginRow input:focus {
border-color: brown;
}
.loginRow button {
width: calc(50% - 15px);
background-color: darkblue;
border: 0;
margin: 0;
padding: 0;
color: white;
height: 30px;
}
.loginRow button:last-of-type{
float: right;
}
+1
View File
@@ -10,6 +10,7 @@
@using PythonInterop
@using WebServer
@using WebServer.Components
@using BCrypt.Net;
@inject DbDriver dbDriver
@inject AIModule aiModule
+30 -25
View File
@@ -2,18 +2,19 @@ using Microsoft.Data.Sqlite;
namespace DataBase {
public class DbDriver : IDisposable {
public class DbDriver {
static SqliteConnection? singletonConnector = null;
static bool Initilized = false;
public DbDriver() {
// Load in the datastore if not already loaded
if (singletonConnector == null) {
singletonConnector = new SqliteConnection("Data Source=test.db");
singletonConnector.Open();
if (!Initilized) {
// Open a connection with auto dispose when done
using (SqliteConnection sqlite = new SqliteConnection("Data Source=test.db")) {
// Open the connection
sqlite.Open();
// Create the key value store if not exist
using var command = singletonConnector.CreateCommand();
using var command = sqlite.CreateCommand();
command.CommandText = """
CREATE TABLE IF NOT EXISTS KeyValuePair (
key TEXT NOT NULL,
@@ -21,33 +22,47 @@ namespace DataBase {
PRIMARY KEY(key)
);
""";
// Run the command
command.ExecuteReader();
}
}
}
// Return Values from keys
public string Get(string Key) {
if (singletonConnector != null) {
using var command = singletonConnector.CreateCommand();
// Open a connection with auto dispose when done
using (SqliteConnection sqlite = new SqliteConnection("Data Source=test.db")) {
// Open the connection
sqlite.Open();
// Create the key value store if not exist
using var command = sqlite.CreateCommand();
command.CommandText = """
SELECT value
FROM KeyValuePair
Where key = $key;
""";
// Add the parameter to prevent sql injection
command.Parameters.AddWithValue("$key", Key);
command.ExecuteReader();
// Run the command
using var reader = command.ExecuteReader();
// Read off the result if exists
if (reader.Read()) {
// Return the results
return reader.GetString(0);
}
}
// Return something if nothing existed
return "";
}
}
// Set a key value pair in the store
public void Set(string Key, string Value) {
if (singletonConnector != null) {
using var command = singletonConnector.CreateCommand();
// Open a connection with auto dispose when done
using (SqliteConnection sqlite = new SqliteConnection("Data Source=test.db")) {
// Open the connection
sqlite.Open();
// Create the key value store if not exist
using var command = sqlite.CreateCommand();
command.CommandText = """
INSERT INTO KeyValuePair
(key,value)
@@ -57,24 +72,14 @@ namespace DataBase {
DO UPDATE
SET value = excluded.value;
""";
// Add the parameters to prevent sql injection
command.Parameters.AddWithValue("$key", Key);
command.Parameters.AddWithValue("$value", Value);
// Process the command
command.ExecuteReader();
}
}
// Deconstructor
~DbDriver() {
Dispose();
}
// Deconstructor
public void Dispose() {
if (singletonConnector != null) {
singletonConnector.Close();
singletonConnector = null;
}
}
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ if (args.Contains("Pull-Stock-Data")) {
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
// Insert the DB driver for Dependency Injection
builder.Services.AddSingleton<DbDriver>();
builder.Services.AddScoped<DbDriver>();
// Insert the python modlue for Dependency Injection
builder.Services.AddSingleton(interopModule);
+1
View File
@@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.1.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.3" />
<PackageReference Include="pythonnet" Version="3.0.5" />
</ItemGroup>