feat: Implement state persistence by serializing game and user data to state.json using serde_json.

This commit is contained in:
2025-12-03 14:40:05 +01:00
parent f9a4dd0851
commit 434eef5e1c
5 changed files with 104 additions and 29 deletions
+65 -25
View File
@@ -1,5 +1,7 @@
use bincode::{Decode, Encode};
use rocket::fs::FileServer;
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::BufReader;
use std::sync::Mutex;
use crate::items::Game;
@@ -14,7 +16,7 @@ pub mod items {
mod auth;
mod proto_utils;
#[derive(Clone)]
#[derive(Clone, Serialize, Deserialize)]
pub struct User {
pub person: items::Person,
pub password_hash: String,
@@ -28,6 +30,34 @@ impl std::ops::Deref for User {
}
}
#[derive(Serialize, Deserialize)]
struct PersistentState {
games: Vec<Game>,
users: Vec<User>,
}
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<Game>, Vec<User>)> {
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("/<name>")]
fn get_user(
_token: auth::Token,
@@ -64,10 +94,14 @@ fn get_game(
#[post("/opinion", data = "<req>")]
fn add_opinion(
token: auth::Token,
user_list: &rocket::State<Mutex<Vec<User>>>,
game_list: &rocket::State<Vec<Game>>,
req: proto_utils::Proto<items::AddOpinionRequest>,
) -> Option<items::Person> {
let mut users = user_list.lock().unwrap();
let mut result = None;
if let Some(user) = users.iter_mut().find(|u| u.person.name == token.username) {
let req = req.into_inner();
let opinion = items::Opinion {
@@ -85,10 +119,13 @@ fn add_opinion(
} else {
user.person.opinion.push(opinion);
}
Some(user.person.clone())
} else {
None
result = Some(user.person.clone());
}
if result.is_some() {
save_state(game_list, &users);
}
result
}
#[get("/<_..>", rank = 20)]
@@ -105,28 +142,31 @@ async fn index_fallback() -> Option<rocket::fs::NamedFile> {
#[rocket::main]
async fn main() -> Result<(), std::io::Error> {
let mut game_list: Vec<Game> = Vec::new();
let mut user_list: Vec<User> = Vec::new();
let (game_list, user_list) = load_state().unwrap_or_else(|| {
let mut game_list: Vec<Game> = Vec::new();
let mut user_list: Vec<User> = Vec::new();
game_list.push(Game {
title: "Naramo Nuclear Plant V2".to_string(),
source: items::Source::Roblox.into(),
multiplayer: true,
min_players: 1,
max_players: 90,
price: 0,
remote_id: 0,
});
game_list.push(Game {
title: "Naramo Nuclear Plant V2".to_string(),
source: items::Source::Roblox.into(),
multiplayer: true,
min_players: 1,
max_players: 90,
price: 0,
remote_id: 0,
});
user_list.push(User {
person: items::Person {
name: "John".to_string(),
opinion: vec![items::Opinion {
title: "Naramo Nuclear Plant V2".to_string(),
would_play: true,
}],
},
password_hash: bcrypt::hash("password123", bcrypt::DEFAULT_COST).unwrap(),
user_list.push(User {
person: items::Person {
name: "John".to_string(),
opinion: vec![items::Opinion {
title: "Naramo Nuclear Plant V2".to_string(),
would_play: true,
}],
},
password_hash: bcrypt::hash("password123", bcrypt::DEFAULT_COST).unwrap(),
});
(game_list, user_list)
});
rocket::build()