From d1a0050f45c01f4944b97183a8c4630496d95047 Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Mon, 1 Dec 2025 16:38:33 -0700 Subject: [PATCH] Add server database interaction --- .gitignore | 2 + unshell-gui/Trunk.toml | 4 -- unshell-gui/src/auth/mod.rs | 18 ++++-- unshell-gui/src/config/mod.rs | 28 +++++++-- unshell-server/Cargo.lock | 107 +++++++++++++++++++++++++++++++-- unshell-server/Cargo.toml | 1 + unshell-server/src/api/app.rs | 57 +++++++++++++----- unshell-server/src/database.rs | 36 ++++++++++- unshell-server/src/lib.rs | 2 +- unshell-server/src/main.rs | 12 +++- 10 files changed, 229 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index efa51a1..48ad36c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +database/ + # Generated by Cargo # will have compiled files and executables /target diff --git a/unshell-gui/Trunk.toml b/unshell-gui/Trunk.toml index 3a34e9b..ed8064e 100644 --- a/unshell-gui/Trunk.toml +++ b/unshell-gui/Trunk.toml @@ -18,7 +18,3 @@ backend = "http://localhost:3000/api" # Address to proxy requests to # no_system_proxy = false # Disable system proxy # rewrite = "" # Strip the given prefix off paths # no_redirect = false # Disable following redirects of proxy responses - - -[[proxy]] -backend = "http://localhost:3000/auth" diff --git a/unshell-gui/src/auth/mod.rs b/unshell-gui/src/auth/mod.rs index fb5fb17..1937a86 100644 --- a/unshell-gui/src/auth/mod.rs +++ b/unshell-gui/src/auth/mod.rs @@ -1,6 +1,7 @@ use egui::{Align2, Area, Color32, Frame, Order, Sense, UiKind, Vec2, mutex::Mutex}; +use serde::{Deserialize, Serialize, de::DeserializeOwned}; use serde_json::json; -use std::sync::Arc; +use std::{marker::PhantomData, sync::Arc}; use wasm_bindgen::prelude::Closure; #[derive(serde::Deserialize, serde::Serialize)] @@ -114,7 +115,7 @@ impl Auth { let state = self.auth_state.clone(); crate::httpPost( - "/auth", + "/api/auth", &json!({ "username": self.username.clone(), "password": self.password.clone() @@ -149,15 +150,24 @@ impl Auth { // }); } - pub fn test(&self) { + pub fn get(&self, path: &str, ret: F) + where + F: FnOnce(Result) + 'static, + T: DeserializeOwned, + { if let Some(ref token) = self.token { let state = self.auth_state.clone(); crate::httpGetAuth( - "/api/test1234/kjhejwer/kwherjwer/iuwehrhiwer/wiuerhjwer", + path, format!("Bearer {}", token.token), Closure::once_into_js(move |ok: bool, response: String| { if ok { crate::log(&response); + if let Ok(value) = serde_json::from_str::>(&response) { + ret(value) + } else { + *(state.lock()) = AuthState::Error("Malformed Response".into()); + } } else { *(state.lock()) = AuthState::Error(response); } diff --git a/unshell-gui/src/config/mod.rs b/unshell-gui/src/config/mod.rs index 19cd7d9..1ad1dba 100644 --- a/unshell-gui/src/config/mod.rs +++ b/unshell-gui/src/config/mod.rs @@ -1,14 +1,34 @@ +use std::sync::{Arc, Mutex}; + +use egui::Color32; + use crate::auth::Auth; -#[derive(Default, serde::Deserialize, serde::Serialize)] +#[derive(serde::Deserialize, serde::Serialize)] pub struct Config { - response_text: String, + response_text: Arc>, +} + +impl Default for Config { + fn default() -> Self { + Self { + response_text: Arc::new(Mutex::new("NONE".to_string())), + } + } } impl Config { pub fn update(&mut self, auth: &mut Auth, ui: &mut egui::Ui) { - if ui.button("Test").clicked() { - auth.test(); + if ui.button("Set Value").clicked() { + let text_clone = self.response_text.clone(); + auth.get("/api/test", move |response: Result| { + *text_clone.lock().unwrap() = format!("{:?}", response); + }); } + + ui.horizontal(|ui| { + ui.label("Response: "); + ui.colored_label(Color32::WHITE, &*self.response_text.lock().unwrap()); + }); } } diff --git a/unshell-server/Cargo.lock b/unshell-server/Cargo.lock index cf4a34d..d128a93 100644 --- a/unshell-server/Cargo.lock +++ b/unshell-server/Cargo.lock @@ -428,6 +428,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -437,6 +446,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -493,6 +511,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -532,6 +560,15 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -732,6 +769,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -887,6 +933,17 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -894,7 +951,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.12", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -905,7 +976,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link", ] @@ -1015,6 +1086,15 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -1195,6 +1275,22 @@ dependencies = [ "time", ] +[[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot 0.11.2", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -1220,8 +1316,8 @@ dependencies = [ "bitflags 1.3.2", "cfg_aliases 0.2.1", "libc", - "parking_lot", - "parking_lot_core", + "parking_lot 0.12.5", + "parking_lot_core 0.9.12", "static_init_macro", "winapi", ] @@ -1339,7 +1435,7 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot", + "parking_lot 0.12.5", "pin-project-lite", "signal-hook-registry", "socket2", @@ -1469,6 +1565,7 @@ dependencies = [ "jsonwebtoken", "serde", "serde_json", + "sled", "static_init", "tokio", "unshell-lib", diff --git a/unshell-server/Cargo.toml b/unshell-server/Cargo.toml index 58f455b..745db50 100644 --- a/unshell-server/Cargo.toml +++ b/unshell-server/Cargo.toml @@ -23,6 +23,7 @@ bcrypt = "0.17.1" chrono = "0.4.42" static_init = "1.0.4" clap = {version = "4.5.53", features = ["derive"]} +sled = "0.34.7" # tracing = "0.1.43" # tracing-subscriber = {version="0.3.22", features = ["env-filter"] } diff --git a/unshell-server/src/api/app.rs b/unshell-server/src/api/app.rs index 513b0b7..61caef9 100644 --- a/unshell-server/src/api/app.rs +++ b/unshell-server/src/api/app.rs @@ -1,6 +1,6 @@ use axum::{ - Extension, Router, - extract::Path, + Extension, Json, Router, + extract::{Path, State}, middleware, response::IntoResponse, routing::{get, post}, @@ -8,34 +8,59 @@ use axum::{ use tokio::net::TcpListener; use unshell_lib::info; -use crate::api::{auth, structs::CurrentUser}; +use crate::{ + api::{auth, structs::CurrentUser}, + database::Database, +}; -pub async fn start_api(address: &str) { +pub async fn start_api(address: &str, database: Database) { let listener = TcpListener::bind(address) .await .expect("Unable to start listener"); info!("Listening on {}", listener.local_addr().unwrap()); - let app = Router::new().route("/auth", post(auth::sign_in)).route( - "/api/{*path}", - get(protected).layer(middleware::from_fn(auth::authorize)), - ); + let app = Router::new() + .route("/api/auth", post(auth::sign_in)) + .route( + "/api/{*path}", + get(get_data).layer(middleware::from_fn(auth::authorize)), + ) + .route( + "/api/{*path}", + post(post_data).layer(middleware::from_fn(auth::authorize)), + ) + .with_state(database); axum::serve(listener, app) .await .expect("Error serving application"); } -pub async fn protected( +pub async fn get_data( + State(database): State, Path(path): Path, Extension(_): Extension, ) -> impl IntoResponse { - info!("{}", path); - // Json(UserResponse { - // email: currentUser.email, - // first_name: currentUser.first_name, - // last_name: currentUser.last_name, - // }) - "Test" + let result = database.get_value(&path); + + Json(serde_json::to_value(result).unwrap()) } + +pub async fn post_data( + State(database): State, + Path(path): Path, + Extension(_): Extension, + body: String, +) -> impl IntoResponse { + let result = database.put_value(&path, &body); + + Json(serde_json::to_value(result).unwrap()) +} + +// impl IntoResponse for Option { +// // impl IntoResponse for Option { +// fn into_response(self) -> axum::response::Response { +// todo!() +// } +// } diff --git a/unshell-server/src/database.rs b/unshell-server/src/database.rs index cbfddc7..13fe6aa 100644 --- a/unshell-server/src/database.rs +++ b/unshell-server/src/database.rs @@ -1,5 +1,37 @@ -// Calc 3 ends Tuesday next week +use unshell_lib::error; +#[derive(Clone)] pub struct Database { - // db: + db: sled::Db, +} + +impl Database { + pub fn new(database: String) -> Self { + Self { + db: sled::open(database).expect("Failed to open database"), + } + } + + pub fn put_value(&self, key: &str, value: &str) -> Result<(), String> { + match self.db.insert(key, value) { + Ok(_) => Ok(()), + Err(e) => { + error!("Failed to load '{}' from database: {}", key, e); + Err(e.to_string()) + } + } + } + + pub fn get_value(&self, key: &str) -> Result { + match self.db.get(key) { + Ok(v) => match v { + Some(v) => Ok(String::from_utf8_lossy(&v.to_vec()).to_string()), + None => Err(format!("Could not find key '{}'", key)), + }, + Err(e) => { + error!("Failed to load '{}' from database: {}", key, e); + Err(e.to_string()) + } + } + } } diff --git a/unshell-server/src/lib.rs b/unshell-server/src/lib.rs index f6fb759..e3877fe 100644 --- a/unshell-server/src/lib.rs +++ b/unshell-server/src/lib.rs @@ -1,5 +1,5 @@ // #![macro_use] mod api; -mod database; +pub mod database; pub use api::app::start_api; diff --git a/unshell-server/src/main.rs b/unshell-server/src/main.rs index 35c6475..6c687a7 100644 --- a/unshell-server/src/main.rs +++ b/unshell-server/src/main.rs @@ -1,10 +1,12 @@ -use unshell_server::start_api; +use unshell_server::{database::Database, start_api}; use clap::Parser; use static_init::dynamic; #[dynamic] static DEFAULT_HOST: String = "localhost".to_string(); +#[dynamic] +static DATABASE_NAME: String = "database".to_string(); /// A fictional versioning CLI #[derive(Debug, Parser)] @@ -18,6 +20,10 @@ pub struct Args { /// Port to listen #[arg(short, long, default_value_t = 3000)] port: usize, + + /// Name of database folder + #[clap(short, long, default_value_t = DATABASE_NAME.clone())] + database_name: String, } #[tokio::main] @@ -26,5 +32,7 @@ async fn main() { unshell_lib::logger::PrettyLogger::init(); - start_api(&format!("{}:{}", args.host, args.port)).await; + let database = Database::new(args.database_name); + + start_api(&format!("{}:{}", args.host, args.port), database).await; }