From f49900adad4d66312b979960be4c9206c6b82f0b Mon Sep 17 00:00:00 2001 From: code002lover Date: Thu, 4 Dec 2025 00:17:28 +0100 Subject: [PATCH] feat: Implement user management with a new `add_user` binary and refactor state handling into a dedicated store module. --- backend/Cargo.toml | 2 ++ backend/src/bin/add_user.rs | 55 ++++++++++++++++++++++++++++++++++ backend/src/lib.rs | 12 ++++++++ backend/src/main.rs | 59 ++++--------------------------------- backend/src/store.rs | 46 +++++++++++++++++++++++++++++ 5 files changed, 120 insertions(+), 54 deletions(-) create mode 100644 backend/src/bin/add_user.rs create mode 100644 backend/src/lib.rs create mode 100644 backend/src/store.rs diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 5f1fa04..5e7888c 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -2,6 +2,8 @@ name = "backend" version = "0.1.0" edition = "2024" +default-run = "backend" + [dependencies] prost = "0.14.1" diff --git a/backend/src/bin/add_user.rs b/backend/src/bin/add_user.rs new file mode 100644 index 0000000..505cc6a --- /dev/null +++ b/backend/src/bin/add_user.rs @@ -0,0 +1,55 @@ +use backend::items::Person; +use backend::store::{self, User, save_state}; +use std::io::{self, Write}; + +fn main() { + println!("Add User Tool"); + println!("-------------"); + + print!("Username: "); + io::stdout().flush().unwrap(); + let mut username = String::new(); + io::stdin().read_line(&mut username).unwrap(); + let username = username.trim().to_string(); + + if username.is_empty() { + println!("Username cannot be empty."); + return; + } + + print!("Password: "); + io::stdout().flush().unwrap(); + let mut password = String::new(); + io::stdin().read_line(&mut password).unwrap(); + let password = password.trim().to_string(); + + if password.is_empty() { + println!("Password cannot be empty."); + return; + } + + let (games, mut users) = store::load_state().unwrap_or_else(|| { + println!("No existing state found. Creating new state."); + (Vec::new(), Vec::new()) + }); + + if users.iter().any(|u| u.person.name == username) { + println!("User '{}' already exists.", username); + return; + } + + let password_hash = bcrypt::hash(&password, bcrypt::DEFAULT_COST).unwrap(); + + let new_user = User { + person: Person { + name: username.clone(), + opinion: Vec::new(), + }, + password_hash, + }; + + users.push(new_user); + save_state(&games, &users); + + println!("User '{}' added successfully.", username); +} diff --git a/backend/src/lib.rs b/backend/src/lib.rs new file mode 100644 index 0000000..08c2ef6 --- /dev/null +++ b/backend/src/lib.rs @@ -0,0 +1,12 @@ +#[macro_use] +extern crate rocket; + +pub mod items { + include!(concat!(env!("OUT_DIR"), "/items.rs")); +} + +pub mod auth; +pub mod proto_utils; +pub mod store; + +pub use store::User; diff --git a/backend/src/main.rs b/backend/src/main.rs index d34d3a7..f856506 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,63 +1,14 @@ use rocket::fs::FileServer; -use serde::{Deserialize, Serialize}; -use std::fs::File; -use std::io::BufReader; use std::sync::Mutex; -use crate::items::Game; +use backend::auth; +use backend::items::{self, Game}; +use backend::proto_utils; +use backend::store::{self, User, save_state}; #[macro_use] extern crate rocket; -pub mod items { - include!(concat!(env!("OUT_DIR"), "/items.rs")); -} - -mod auth; -mod proto_utils; - -#[derive(Clone, Serialize, Deserialize)] -pub struct User { - pub person: items::Person, - pub password_hash: String, -} - -impl std::ops::Deref for User { - type Target = items::Person; - - fn deref(&self) -> &Self::Target { - &self.person - } -} - -#[derive(Serialize, Deserialize)] -struct PersistentState { - games: Vec, - users: Vec, -} - -const STATE_FILE: &str = "state.json"; - -fn save_state(games: &[Game], users: &[User]) { - let state = PersistentState { - games: games.to_vec(), - users: users.to_vec(), - }; - if let Ok(file) = File::create(STATE_FILE) { - let _ = serde_json::to_writer_pretty(file, &state); - } -} - -fn load_state() -> Option<(Vec, Vec)> { - if let Ok(file) = File::open(STATE_FILE) { - let reader = BufReader::new(file); - if let Ok(state) = serde_json::from_reader::<_, PersistentState>(reader) { - return Some((state.games, state.users)); - } - } - None -} - #[get("/")] fn get_user( _token: auth::Token, @@ -178,7 +129,7 @@ async fn index_fallback() -> Option { #[rocket::main] async fn main() -> Result<(), std::io::Error> { - let (game_list, user_list) = load_state().unwrap_or_else(|| { + let (game_list, user_list) = store::load_state().unwrap_or_else(|| { let mut game_list: Vec = Vec::new(); let mut user_list: Vec = Vec::new(); diff --git a/backend/src/store.rs b/backend/src/store.rs new file mode 100644 index 0000000..563eefe --- /dev/null +++ b/backend/src/store.rs @@ -0,0 +1,46 @@ +use crate::items::{Game, Person}; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::io::BufReader; + +#[derive(Clone, Serialize, Deserialize)] +pub struct User { + pub person: Person, + pub password_hash: String, +} + +impl std::ops::Deref for User { + type Target = Person; + + fn deref(&self) -> &Self::Target { + &self.person + } +} + +#[derive(Serialize, Deserialize)] +pub struct PersistentState { + pub games: Vec, + pub users: Vec, +} + +pub const STATE_FILE: &str = "state.json"; + +pub fn save_state(games: &[Game], users: &[User]) { + let state = PersistentState { + games: games.to_vec(), + users: users.to_vec(), + }; + if let Ok(file) = File::create(STATE_FILE) { + let _ = serde_json::to_writer_pretty(file, &state); + } +} + +pub fn load_state() -> Option<(Vec, Vec)> { + if let Ok(file) = File::open(STATE_FILE) { + let reader = BufReader::new(file); + if let Ok(state) = serde_json::from_reader::<_, PersistentState>(reader) { + return Some((state.games, state.users)); + } + } + None +}