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

This commit is contained in:
code002lover 2025-12-03 14:40:05 +01:00
parent f9a4dd0851
commit 434eef5e1c
5 changed files with 104 additions and 29 deletions

6
Cargo.lock generated
View File

@ -112,6 +112,8 @@ dependencies = [
"prost-types", "prost-types",
"rocket", "rocket",
"rocket_prost_responder_derive", "rocket_prost_responder_derive",
"serde",
"serde_json",
"uuid", "uuid",
] ]
@ -2041,9 +2043,9 @@ checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.18.1" version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
dependencies = [ dependencies = [
"getrandom 0.3.4", "getrandom 0.3.4",
"js-sys", "js-sys",

View File

@ -9,9 +9,11 @@ prost-types = "0.14.1"
rocket = { git = "https://github.com/rwf2/Rocket", rev = "504efef179622df82ba1dbd37f2e0d9ed2b7c9e4" } rocket = { git = "https://github.com/rwf2/Rocket", rev = "504efef179622df82ba1dbd37f2e0d9ed2b7c9e4" }
bytes = "1" bytes = "1"
rocket_prost_responder_derive = { path = "rocket_prost_responder_derive" } rocket_prost_responder_derive = { path = "rocket_prost_responder_derive" }
uuid = { version = "1.10.0", features = ["v4"] } uuid = { version = "1.19.0", features = ["v4"] }
bcrypt = "0.17.1" bcrypt = "0.17.1"
bincode = "2.0.1" bincode = "2.0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[build-dependencies] [build-dependencies]
prost-build = "0.14.1" prost-build = "0.14.1"

View File

@ -10,7 +10,7 @@ fn main() -> Result<(), std::io::Error> {
cfg.type_attribute( cfg.type_attribute(
".", ".",
"#[derive(rocket_prost_responder_derive::RocketResponder)]", "#[derive(rocket_prost_responder_derive::RocketResponder, serde::Serialize, serde::Deserialize)]",
); );
cfg.compile_protos(&["../protobuf/items.proto"], &["../protobuf"])?; cfg.compile_protos(&["../protobuf/items.proto"], &["../protobuf"])?;

View File

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

31
state.json Normal file
View File

@ -0,0 +1,31 @@
{
"games": [
{
"title": "Naramo Nuclear Plant V2",
"source": 1,
"multiplayer": true,
"min_players": 1,
"max_players": 90,
"price": 0,
"remote_id": 0
}
],
"users": [
{
"person": {
"name": "John",
"opinion": [
{
"title": "Naramo Nuclear Plant V2",
"would_play": true
},
{
"title": "Test2",
"would_play": true
}
]
},
"password_hash": "$2b$12$DRvTP/ibTWULkuJJr285bumRd7SG3n5bYkDpb09Qpklqf6FeTiHkC"
}
]
}