From 30f3aa63d5ba4d74ca7e2b75157bcabd976ba299 Mon Sep 17 00:00:00 2001 From: Matthew Kaminski Date: Fri, 22 Sep 2023 03:45:48 -0400 Subject: [PATCH] Add base data structures for pool matches --- Cargo.toml | 16 ++++++---- src/components/layout.rs | 6 ---- src/data/mod.rs | 1 + src/data/pool_match.rs | 55 +++++++++++++++++++++++++++++--- src/data/store.rs | 22 +++++-------- src/endpoints.rs | 1 - src/handler/mod.rs | 20 ------------ src/main.rs | 37 ++++++++++----------- src/server/mod.rs | 2 ++ src/server/routes.rs | 34 ++++++++++++++++++++ src/templates/add_game_form.rs | 23 ++++++------- src/templates/global_state.rs | 19 +++++------ src/templates/index.rs | 5 +-- src/templates/mod.rs | 4 +-- src/templates/one_v_one_board.rs | 10 +----- src/templates/overall_board.rs | 16 +++------- 16 files changed, 148 insertions(+), 123 deletions(-) delete mode 100644 src/handler/mod.rs create mode 100644 src/server/mod.rs create mode 100644 src/server/routes.rs diff --git a/Cargo.toml b/Cargo.toml index f87e447..c49c59c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,25 +5,29 @@ version = "0.1.0" edition = "2021" [dependencies] -perseus = { version = "0.4.2", features = [ "hydrate" ] } -sycamore = {version = "0.8.2", features = ["suspense", "web", "wasm-bindgen-interning",]} +perseus = { version = "0.4.2", features = ["hydrate"] } +sycamore = { version = "0.8.2", features = [ + "suspense", + "web", + "wasm-bindgen-interning", +] } serde = { version = "1", features = ["derive"] } serde_json = "1" env_logger = "0.10.0" log = "0.4.20" once_cell = "1.18.0" web-sys = "0.3.64" -cfg-if = { version = "1.0.0", features = [] } - +cfg-if = "1.0.0" +chrono = { version = "0.4.31", features = ["serde"] } [target.'cfg(engine)'.dev-dependencies] fantoccini = "0.19" [target.'cfg(engine)'.dependencies] -tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } perseus-axum = { version = "0.4.2" } axum = "0.6" -tower-http = { version = "0.3", features = [ "fs" ] } +tower-http = { version = "0.3", features = ["fs"] } [target.'cfg(client)'.dependencies] wasm-bindgen = "0.2" diff --git a/src/components/layout.rs b/src/components/layout.rs index bf10903..a561828 100644 --- a/src/components/layout.rs +++ b/src/components/layout.rs @@ -2,9 +2,7 @@ use sycamore::prelude::*; #[derive(Prop)] pub struct LayoutProps<'a, G: Html> { - /// The title of the page, which will be displayed in the header. pub title: &'a str, - /// The content to put inside the layout. pub children: Children<'a, G>, } @@ -15,11 +13,7 @@ pub fn Layout<'a, G: Html>( ) -> View { let children = children.call(cx); - // example params - // p { (title.to_string()) } - view! { cx, - // These elements are styled with bright colors for demonstration purposes header { div (class = "flex items-center justify-between") { div (class = "w-full text-gray-700 md:text-center text-2xl font-semibold") { diff --git a/src/data/mod.rs b/src/data/mod.rs index 469501c..68c233a 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,4 +1,5 @@ pub mod pool_match; +pub mod user; #[cfg(engine)] pub mod store; diff --git a/src/data/pool_match.rs b/src/data/pool_match.rs index 31e3e6e..3ddf92d 100644 --- a/src/data/pool_match.rs +++ b/src/data/pool_match.rs @@ -1,11 +1,56 @@ +use crate::data::user::PlayerId; +use chrono::serde::ts_seconds; +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone)] -pub struct PoolMatch { - pub players: Vec, - pub winner: String, +pub type MatchId = u32; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub enum MatchData { + Standard8Ball { + winner: PlayerId, + loser: PlayerId, + }, + Standard9Ball { + winner: PlayerId, + loser: PlayerId, + }, + CutThroat { + winner: PlayerId, + losers: [PlayerId; 2], + }, } -#[derive(Serialize, Deserialize, Clone)] + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct PoolMatch { + pub id: MatchId, + pub data: MatchData, + #[serde(with = "ts_seconds")] + pub time: DateTime, +} +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct PoolMatchList { pub pool_matches: Vec, + pub max_id: MatchId, +} + +impl PoolMatch { + pub fn new(data: MatchData, time: DateTime) -> PoolMatch { + PoolMatch { id: 0, data, time } + } +} + +impl PoolMatchList { + pub fn new() -> PoolMatchList { + PoolMatchList { + pool_matches: vec![], + max_id: 0, + } + } + + pub fn add_pool_match(&mut self, mut pool_match: PoolMatch) { + pool_match.id = self.max_id + 1; + self.max_id += 1; + self.pool_matches.push(pool_match); + } } diff --git a/src/data/store.rs b/src/data/store.rs index 06abbe6..839a49b 100644 --- a/src/data/store.rs +++ b/src/data/store.rs @@ -1,11 +1,9 @@ +// (Server only) In-memory data storage and persistent storage -use once_cell::sync::Lazy; -use std::sync::Mutex; -use serde::{Serialize, Deserialize}; use crate::data::pool_match::PoolMatchList; -use std::fs; -use std::path::Path; - +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; +use std::{fs, path::Path, sync::Mutex}; #[derive(Serialize, Deserialize, Clone)] pub struct Store { @@ -16,11 +14,9 @@ impl Store { fn new() -> Store { fs::create_dir_all("data").unwrap(); match Path::new("data/store.json").exists() { - false => { - Store { - matches: PoolMatchList { pool_matches: vec![] }, - } - } + false => Store { + matches: PoolMatchList::new(), + }, true => { let contents = fs::read_to_string("data/store.json").unwrap(); serde_json::from_str(&contents).unwrap() @@ -35,6 +31,4 @@ impl Store { } } -pub static DATA: Lazy> = Lazy::new(|| { - Mutex::new(Store::new()) -}); +pub static DATA: Lazy> = Lazy::new(|| Mutex::new(Store::new())); diff --git a/src/endpoints.rs b/src/endpoints.rs index 37504c6..5cda3da 100644 --- a/src/endpoints.rs +++ b/src/endpoints.rs @@ -1,2 +1 @@ - pub const MATCH: &str = "/api/post-match"; diff --git a/src/handler/mod.rs b/src/handler/mod.rs deleted file mode 100644 index 3b8b579..0000000 --- a/src/handler/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -use axum::{ - extract::Json, -}; -use crate::data::pool_match::{PoolMatch, PoolMatchList}; -use std::thread; -use crate::data::store::DATA; - -pub async fn post_match(Json(pool_match): Json) -> Json { - // Update the store with the new match - let matches = thread::spawn(move || { - // Get the store - let mut store = DATA.lock().unwrap(); - // Add the match - (*store).matches.pool_matches.push(pool_match); - // Return all pool matches - (*store).matches.clone() - }).join().unwrap(); - - Json(matches) -} diff --git a/src/main.rs b/src/main.rs index cec2899..a5c1e32 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,26 @@ mod components; -mod templates; mod data; +mod endpoints; mod error_views; #[cfg(engine)] -mod handler; -mod endpoints; +mod server; +mod templates; use perseus::prelude::*; use sycamore::prelude::view; -#[cfg(engine)] -use axum::routing::post; -#[cfg(engine)] -use perseus::{ - i18n::TranslationsManager, - server::ServerOptions, - stores::MutableStore, - turbine::Turbine, -}; -#[cfg(engine)] -use crate::endpoints::MATCH; -#[cfg(engine)] -use crate::handler::post_match; - +cfg_if::cfg_if! { + if #[cfg(engine)] { + use std::net::SocketAddr; + use perseus::{ + i18n::TranslationsManager, + server::ServerOptions, + stores::MutableStore, + turbine::Turbine, + }; + use crate::server::routes::register_routes; + } +} #[cfg(engine)] pub async fn dflt_server( @@ -30,13 +28,12 @@ pub async fn dflt_server Router { + let app = app.route(MATCH, post(post_match)); + app +} + +async fn post_match(Json(pool_match): Json) -> Json { + // Update the store with the new match + let matches = thread::spawn(move || { + // Get the store + let mut data = DATA.lock().unwrap(); + (*data).matches.add_pool_match(pool_match); + println!("{:?}", (*data).matches.pool_matches); + (*data).matches.clone() + }) + .join() + .unwrap(); + + Json(matches) +} diff --git a/src/templates/add_game_form.rs b/src/templates/add_game_form.rs index 6ec07bd..285b9a9 100644 --- a/src/templates/add_game_form.rs +++ b/src/templates/add_game_form.rs @@ -1,8 +1,8 @@ -use crate::components::layout::Layout; +use crate::{components::layout::Layout, data::pool_match::MatchData}; use perseus::prelude::*; use serde::{Deserialize, Serialize}; use sycamore::prelude::*; -use web_sys::{Event}; +use web_sys::Event; cfg_if::cfg_if! { if #[cfg(client)] { @@ -10,10 +10,10 @@ cfg_if::cfg_if! { use crate::templates::global_state::AppStateRx; use crate::endpoints::MATCH; use crate::templates::get_api_path; + use chrono::Utc; } } - // Reactive page #[derive(Serialize, Deserialize, Clone, ReactiveState)] @@ -22,20 +22,16 @@ struct PageState { name: String, } - fn add_game_form_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a PageStateRx) -> View { - let handle_add_match = move |_event: Event| { #[cfg(client)] { + // state.name.get().as_ref().clone() spawn_local_scoped(cx, async move { - let new_match = PoolMatch { - players: vec![], - winner: state.name.get().as_ref().clone(), - }; - + let new_match = PoolMatch::new(MatchData::Standard8Ball { winner: 1, loser: 2 }, Utc::now()); let client = reqwest::Client::new(); - let new_matches = client.post(get_api_path(MATCH).as_str()) + let new_matches = client + .post(get_api_path(MATCH).as_str()) .json(&new_match) .send() .await @@ -45,7 +41,6 @@ fn add_game_form_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a PageStat .unwrap(); let global_state = Reactor::::from_cx(cx).get_global_state::(cx); global_state.matches.set(new_matches); - }) } }; @@ -75,7 +70,9 @@ async fn get_request_state( _info: StateGeneratorInfo<()>, _req: Request, ) -> Result> { - Ok(PageState { name: "Ferris".to_string() }) + Ok(PageState { + name: "Ferris".to_string(), + }) } #[engine_only_fn] diff --git a/src/templates/global_state.rs b/src/templates/global_state.rs index 642b2aa..4ac7502 100644 --- a/src/templates/global_state.rs +++ b/src/templates/global_state.rs @@ -1,3 +1,6 @@ +// Not a page, global state that is shared between all pages + +use crate::data::pool_match::PoolMatchList; use perseus::{prelude::*, state::GlobalStateCreator}; use serde::{Deserialize, Serialize}; @@ -5,13 +8,10 @@ cfg_if::cfg_if! { if #[cfg(engine)] { use std::thread; use std::ops::Deref; + use crate::data::store::DATA; } } -#[cfg(engine)] -use crate::data::store::DATA; -use crate::data::pool_match::PoolMatchList; - #[derive(Serialize, Deserialize, ReactiveState, Clone)] #[rx(alias = "AppStateRx")] pub struct AppState { @@ -26,16 +26,13 @@ pub fn get_global_state_creator() -> GlobalStateCreator { #[engine_only_fn] fn get_state() -> AppState { - let matches = thread::spawn(move || { - DATA.lock().unwrap().deref().matches.clone() - }).join().unwrap(); + let matches = thread::spawn(move || DATA.lock().unwrap().deref().matches.clone()) + .join() + .unwrap(); - AppState { - matches - } + AppState { matches } } - #[engine_only_fn] pub async fn get_build_state() -> AppState { get_state() diff --git a/src/templates/index.rs b/src/templates/index.rs index dda860d..ff585f8 100644 --- a/src/templates/index.rs +++ b/src/templates/index.rs @@ -20,8 +20,5 @@ fn head(cx: Scope) -> View { } pub fn get_template() -> Template { - Template::build("") - .view(index_page) - .head(head) - .build() + Template::build("").view(index_page).head(head).build() } diff --git a/src/templates/mod.rs b/src/templates/mod.rs index f2368c1..8e589c5 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -1,8 +1,8 @@ -pub mod index; pub mod add_game_form; +pub mod global_state; +pub mod index; pub mod one_v_one_board; pub mod overall_board; -pub mod global_state; #[cfg(client)] use perseus::utils::get_path_prefix_client; diff --git a/src/templates/one_v_one_board.rs b/src/templates/one_v_one_board.rs index 570e763..cccbe82 100644 --- a/src/templates/one_v_one_board.rs +++ b/src/templates/one_v_one_board.rs @@ -3,18 +3,13 @@ use perseus::prelude::*; use serde::{Deserialize, Serialize}; use sycamore::prelude::*; -// Reactive page - #[derive(Serialize, Deserialize, Clone, ReactiveState)] #[rx(alias = "PageStateRx")] -struct PageState { - -} +struct PageState {} fn one_v_one_board_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, _state: &'a PageStateRx) -> View { view! { cx, Layout(title = "1v1 Leaderboard") { - // Anything we put in here will be rendered inside the `
` block of the layout p { "leaderboard" } } } @@ -35,9 +30,6 @@ fn head(cx: Scope) -> View { } } - -// Template - pub fn get_template() -> Template { Template::build("one-v-one-board") .request_state_fn(get_request_state) diff --git a/src/templates/overall_board.rs b/src/templates/overall_board.rs index c3ab4bb..cd86580 100644 --- a/src/templates/overall_board.rs +++ b/src/templates/overall_board.rs @@ -1,23 +1,18 @@ -use crate::components::layout::Layout; +use crate::{components::layout::Layout, templates::global_state::AppStateRx}; + use perseus::prelude::*; use serde::{Deserialize, Serialize}; use sycamore::prelude::*; -use crate::templates::global_state::AppStateRx; - -// Reactive page #[derive(Serialize, Deserialize, Clone, ReactiveState)] #[rx(alias = "PageStateRx")] -struct PageState { - -} +struct PageState {} fn overall_board_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, _state: &'a PageStateRx) -> View { let global_state = Reactor::::from_cx(cx).get_global_state::(cx); view! { cx, Layout(title = "Overall Leaderboard") { - // Anything we put in here will be rendered inside the `
` block of the layout ul { (View::new_fragment( global_state.matches.get() @@ -29,7 +24,7 @@ fn overall_board_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, _state: &'a PageSta let game = item.clone(); view! { cx, li { - (game.winner) + (game.id) } } }) @@ -55,9 +50,6 @@ fn head(cx: Scope) -> View { } } - -// Template - pub fn get_template() -> Template { Template::build("overall-board") .request_state_fn(get_request_state)