From 3d52cd0a246e8325c7791809e27c3195f7e7f05b Mon Sep 17 00:00:00 2001 From: matkam7 Date: Wed, 20 Sep 2023 21:32:19 -0400 Subject: [PATCH 1/4] Add API --- Cargo.toml | 10 +++++-- README.md | 4 +-- src/data/mod.rs | 3 +- src/data/store.rs | 2 -- src/main.rs | 51 +++++++++++++++++++++++++++++++++- src/templates/add_game_form.rs | 34 ++++++++++++++++++++--- src/templates/mod.rs | 17 ++++++++++++ src/templates/overall_board.rs | 23 +++++---------- 8 files changed, 116 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 50d469a..08bf1cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,18 +6,24 @@ edition = "2021" [dependencies] perseus = { version = "0.4.2", features = [ "hydrate" ] } -sycamore = "0.8.2" +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" + [target.'cfg(engine)'.dev-dependencies] fantoccini = "0.19" [target.'cfg(engine)'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-axum = { version = "0.4.2", features = [ "dflt-server" ] } +perseus-axum = { version = "0.4.2" } +axum = "0.6" +tower-http = { version = "0.3", features = [ "fs" ] } [target.'cfg(client)'.dependencies] +wasm-bindgen = "0.2" +reqwest = "0.11" diff --git a/README.md b/README.md index 4db8f72..4266666 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ https://nodejs.org/en ## 3. Install Perseus, for real-time updates while developing `cargo install perseus-cli` -`cargo build --target wasm32-unknown-unknown` +`rustup target add wasm32-unknown-unknown` ## 4. Install tailwindcss, for styling @@ -43,7 +43,7 @@ To build CSS run: `npm run build` To build the project for testing, run -`perseus serve` +`perseus serve --verbose` # Deploying the project diff --git a/src/data/mod.rs b/src/data/mod.rs index 733a262..e7ca638 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,2 +1,3 @@ pub mod pool_match; -pub mod store; \ No newline at end of file +pub mod store; +pub mod global_state; diff --git a/src/data/store.rs b/src/data/store.rs index a3c3889..f1016b9 100644 --- a/src/data/store.rs +++ b/src/data/store.rs @@ -1,5 +1,3 @@ -#![cfg(engine)] - use std::collections::HashMap; use once_cell::sync::Lazy; use std::sync::Mutex; diff --git a/src/main.rs b/src/main.rs index e7e5f1f..116d524 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,11 +6,60 @@ mod error_views; use perseus::prelude::*; use sycamore::prelude::view; -#[perseus::main(perseus_axum::dflt_server)] +#[cfg(engine)] +use axum::{ + body::Body, + extract::{Path, Query}, + http::{Request, StatusCode}, + response::{IntoResponse, Response}, + routing::{get, get_service}, + Router, +}; +#[cfg(engine)] +use perseus::turbine::ApiResponse as PerseusApiResponse; +#[cfg(engine)] +use perseus::{ + i18n::TranslationsManager, + path::*, + server::ServerOptions, + stores::MutableStore, + turbine::{SubsequentLoadQueryParams, Turbine}, +}; +#[cfg(engine)] +use tower_http::services::{ServeDir, ServeFile}; + + + +async fn print_something() { + println!("haha"); +} + +#[cfg(engine)] +pub async fn dflt_server( + turbine: &'static Turbine, + opts: ServerOptions, + (host, port): (String, u16), +) { + use std::net::SocketAddr; + + let addr: SocketAddr = format!("{}:{}", host, port) + .parse() + .expect("Invalid address provided to bind to."); + let mut app = perseus_axum::get_router(turbine, opts).await; + app = app.route("/api/test", get(print_something)); + + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} + +#[perseus::main(dflt_server)] pub fn main() -> PerseusApp { env_logger::init(); PerseusApp::new() + .global_state_creator(crate::data::global_state::get_global_state_creator()) .template(crate::templates::index::get_template()) .template(crate::templates::add_game_form::get_template()) .template(crate::templates::one_v_one_board::get_template()) diff --git a/src/templates/add_game_form.rs b/src/templates/add_game_form.rs index 3fb2d8e..017e798 100644 --- a/src/templates/add_game_form.rs +++ b/src/templates/add_game_form.rs @@ -1,21 +1,48 @@ +use std::ops::Deref; use crate::components::layout::Layout; use perseus::prelude::*; use serde::{Deserialize, Serialize}; use sycamore::prelude::*; +use crate::data::global_state::AppStateRx; +use web_sys::{window, Event}; +use crate::data::pool_match::PoolMatch; +#[cfg(client)] +use perseus::utils::get_path_prefix_client; +use crate::templates::get_api_path; + // Reactive page #[derive(Serialize, Deserialize, Clone, ReactiveState)] #[rx(alias = "PageStateRx")] -struct PageState { +struct PageState {} + -} fn add_game_form_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a PageStateRx) -> View { + let global_state = Reactor::::from_cx(cx).get_global_state::(cx); + let api_path = get_api_path("/api/test"); + + let handle_add_match = move |event: Event| { + #[cfg(client)] + { + let path = get_api_path("/api/test"); + println!("{}", path); + spawn_local_scoped(cx, async move { + reqwest::get(get_api_path("/api/test").as_str()).await.unwrap(); + }) + } + }; + view! { cx, Layout(title = "Add Game Results") { // Anything we put in here will be rendered inside the `
` block of the layout - p { "Results" } + button(on:click=handle_add_match) { + "Add result" + } + p { + (api_path) + } } } } @@ -35,7 +62,6 @@ fn head(cx: Scope) -> View { } } - // Template pub fn get_template() -> Template { diff --git a/src/templates/mod.rs b/src/templates/mod.rs index 697dd20..19b6c4e 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -2,3 +2,20 @@ pub mod index; pub mod add_game_form; pub mod one_v_one_board; pub mod overall_board; + +#[cfg(client)] +use perseus::utils::get_path_prefix_client; + +pub fn get_api_path(path: &str) -> String { + #[cfg(engine)] + { + path.to_string() + } + #[cfg(client)] + { + let path = web_sys::window().unwrap().location().pathname().unwrap(); + // let base_path = get_path_prefix_client(); + // format!("{}{}", base_path, path) + path.to_string() + } +} \ No newline at end of file diff --git a/src/templates/overall_board.rs b/src/templates/overall_board.rs index acbed55..f8024cf 100644 --- a/src/templates/overall_board.rs +++ b/src/templates/overall_board.rs @@ -6,6 +6,7 @@ use crate::data::store::DATA; #[cfg(engine)] use std::thread; use sycamore::prelude::*; +use crate::data::global_state::AppStateRx; use crate::data::pool_match::{ PoolMatchList, PoolMatch @@ -16,16 +17,19 @@ use crate::data::pool_match::{ #[derive(Serialize, Deserialize, Clone, ReactiveState)] #[rx(alias = "PageStateRx")] struct PageState { - matches: PoolMatchList, + } 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( - state.matches.get() + global_state.store.get() + .matches .pool_matches .iter() .rev() @@ -50,20 +54,7 @@ async fn get_request_state( _info: StateGeneratorInfo<()>, req: Request, ) -> Result> { - - let matches = thread::spawn(move || { - let mut db = DATA.lock().unwrap(); - db.matches.pool_matches.push(PoolMatch { - players: vec![], - winner: "lol".to_string(), - }); - db.write(); - db.matches.clone() - }).join().unwrap(); - - Ok(PageState { - matches - }) + Ok(PageState {}) } #[engine_only_fn] -- 2.49.1 From 30b637dfefe89fa76756b52aea4ca16f8118ad48 Mon Sep 17 00:00:00 2001 From: matkam7 Date: Wed, 20 Sep 2023 21:33:12 -0400 Subject: [PATCH 2/4] Add cargo file --- .cargo/config.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..5fba26e --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = [ "--cfg", "engine" ] -- 2.49.1 From 0a68829d6cc07ba9f9f0a3a16d8909117535b8e4 Mon Sep 17 00:00:00 2001 From: matkam7 Date: Thu, 21 Sep 2023 20:03:16 -0400 Subject: [PATCH 3/4] Add working API calls, fix warnings --- Cargo.toml | 3 +- src/components/layout.rs | 2 +- src/data/mod.rs | 3 +- src/data/store.rs | 8 ++-- src/endpoints.rs | 2 + src/handler/mod.rs | 20 ++++++++++ src/main.rs | 30 +++++--------- src/templates/add_game_form.rs | 67 +++++++++++++++++++++----------- src/templates/global_state.rs | 47 ++++++++++++++++++++++ src/templates/mod.rs | 11 +++--- src/templates/one_v_one_board.rs | 4 +- src/templates/overall_board.rs | 19 +++------ 12 files changed, 147 insertions(+), 69 deletions(-) create mode 100644 src/endpoints.rs create mode 100644 src/handler/mod.rs create mode 100644 src/templates/global_state.rs diff --git a/Cargo.toml b/Cargo.toml index 08bf1cd..f87e447 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ 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 = [] } [target.'cfg(engine)'.dev-dependencies] @@ -26,4 +27,4 @@ tower-http = { version = "0.3", features = [ "fs" ] } [target.'cfg(client)'.dependencies] wasm-bindgen = "0.2" -reqwest = "0.11" +reqwest = { version = "0.11", features = ["json"] } diff --git a/src/components/layout.rs b/src/components/layout.rs index 383ac75..bf10903 100644 --- a/src/components/layout.rs +++ b/src/components/layout.rs @@ -11,7 +11,7 @@ pub struct LayoutProps<'a, G: Html> { #[component] pub fn Layout<'a, G: Html>( cx: Scope<'a>, - LayoutProps { title, children }: LayoutProps<'a, G>, + LayoutProps { title: _, children }: LayoutProps<'a, G>, ) -> View { let children = children.call(cx); diff --git a/src/data/mod.rs b/src/data/mod.rs index e7ca638..469501c 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,3 +1,4 @@ pub mod pool_match; + +#[cfg(engine)] pub mod store; -pub mod global_state; diff --git a/src/data/store.rs b/src/data/store.rs index f1016b9..06abbe6 100644 --- a/src/data/store.rs +++ b/src/data/store.rs @@ -1,8 +1,8 @@ -use std::collections::HashMap; + use once_cell::sync::Lazy; use std::sync::Mutex; use serde::{Serialize, Deserialize}; -use crate::data::pool_match::{PoolMatchList, PoolMatch}; +use crate::data::pool_match::PoolMatchList; use std::fs; use std::path::Path; @@ -14,7 +14,7 @@ pub struct Store { impl Store { fn new() -> Store { - fs::create_dir_all("data"); + fs::create_dir_all("data").unwrap(); match Path::new("data/store.json").exists() { false => { Store { @@ -27,6 +27,8 @@ impl Store { } } } + // TODO -> Store data + #[allow(dead_code)] pub fn write(&self) { let contents = serde_json::to_string(&self).unwrap(); fs::write("data/store.json", contents).unwrap(); diff --git a/src/endpoints.rs b/src/endpoints.rs new file mode 100644 index 0000000..37504c6 --- /dev/null +++ b/src/endpoints.rs @@ -0,0 +1,2 @@ + +pub const MATCH: &str = "/api/post-match"; diff --git a/src/handler/mod.rs b/src/handler/mod.rs new file mode 100644 index 0000000..3b8b579 --- /dev/null +++ b/src/handler/mod.rs @@ -0,0 +1,20 @@ +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 116d524..cec2899 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,38 +2,28 @@ mod components; mod templates; mod data; mod error_views; +#[cfg(engine)] +mod handler; +mod endpoints; use perseus::prelude::*; use sycamore::prelude::view; #[cfg(engine)] -use axum::{ - body::Body, - extract::{Path, Query}, - http::{Request, StatusCode}, - response::{IntoResponse, Response}, - routing::{get, get_service}, - Router, -}; -#[cfg(engine)] -use perseus::turbine::ApiResponse as PerseusApiResponse; +use axum::routing::post; #[cfg(engine)] use perseus::{ i18n::TranslationsManager, - path::*, server::ServerOptions, stores::MutableStore, - turbine::{SubsequentLoadQueryParams, Turbine}, + turbine::Turbine, }; #[cfg(engine)] -use tower_http::services::{ServeDir, ServeFile}; +use crate::endpoints::MATCH; +#[cfg(engine)] +use crate::handler::post_match; - -async fn print_something() { - println!("haha"); -} - #[cfg(engine)] pub async fn dflt_server( turbine: &'static Turbine, @@ -46,7 +36,7 @@ pub async fn dflt_server() -> PerseusApp { env_logger::init(); PerseusApp::new() - .global_state_creator(crate::data::global_state::get_global_state_creator()) + .global_state_creator(crate::templates::global_state::get_global_state_creator()) .template(crate::templates::index::get_template()) .template(crate::templates::add_game_form::get_template()) .template(crate::templates::one_v_one_board::get_template()) diff --git a/src/templates/add_game_form.rs b/src/templates/add_game_form.rs index 017e798..6ec07bd 100644 --- a/src/templates/add_game_form.rs +++ b/src/templates/add_game_form.rs @@ -1,47 +1,70 @@ -use std::ops::Deref; use crate::components::layout::Layout; use perseus::prelude::*; use serde::{Deserialize, Serialize}; use sycamore::prelude::*; -use crate::data::global_state::AppStateRx; -use web_sys::{window, Event}; -use crate::data::pool_match::PoolMatch; -#[cfg(client)] -use perseus::utils::get_path_prefix_client; -use crate::templates::get_api_path; +use web_sys::{Event}; + +cfg_if::cfg_if! { + if #[cfg(client)] { + use crate::data::pool_match::{PoolMatch, PoolMatchList}; + use crate::templates::global_state::AppStateRx; + use crate::endpoints::MATCH; + use crate::templates::get_api_path; + } +} // Reactive page #[derive(Serialize, Deserialize, Clone, ReactiveState)] #[rx(alias = "PageStateRx")] -struct PageState {} - +struct PageState { + name: String, +} fn add_game_form_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a PageStateRx) -> View { - let global_state = Reactor::::from_cx(cx).get_global_state::(cx); - let api_path = get_api_path("/api/test"); - let handle_add_match = move |event: Event| { + let handle_add_match = move |_event: Event| { #[cfg(client)] { - let path = get_api_path("/api/test"); - println!("{}", path); spawn_local_scoped(cx, async move { - reqwest::get(get_api_path("/api/test").as_str()).await.unwrap(); + let new_match = PoolMatch { + players: vec![], + winner: state.name.get().as_ref().clone(), + }; + + let client = reqwest::Client::new(); + let new_matches = client.post(get_api_path(MATCH).as_str()) + .json(&new_match) + .send() + .await + .unwrap() + .json::() + .await + .unwrap(); + let global_state = Reactor::::from_cx(cx).get_global_state::(cx); + global_state.matches.set(new_matches); + }) } }; view! { cx, Layout(title = "Add Game Results") { - // Anything we put in here will be rendered inside the `
` block of the layout - button(on:click=handle_add_match) { - "Add result" + div (class = "flex flex-wrap") { + input (bind:value = state.name, + class = "appearance-none block w-full bg-gray-200 text-gray-700 border \ + border-red-500 rounded py-3 px-4 mb-3 leading-tight focus:outline-none \ + focus:bg-white",) } - p { - (api_path) + div (class = "flex flex-wrap") { + button(on:click = handle_add_match, + class = "flex-shrink-0 bg-teal-500 hover:bg-teal-700 border-teal-500 \ + hover:border-teal-700 text-sm border-4 text-white py-1 px-2 rounded", + ) { + "Add result" + } } } } @@ -50,9 +73,9 @@ fn add_game_form_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a PageStat #[engine_only_fn] async fn get_request_state( _info: StateGeneratorInfo<()>, - req: Request, + _req: Request, ) -> Result> { - Ok(PageState {}) + Ok(PageState { name: "Ferris".to_string() }) } #[engine_only_fn] diff --git a/src/templates/global_state.rs b/src/templates/global_state.rs new file mode 100644 index 0000000..642b2aa --- /dev/null +++ b/src/templates/global_state.rs @@ -0,0 +1,47 @@ +use perseus::{prelude::*, state::GlobalStateCreator}; +use serde::{Deserialize, Serialize}; + +cfg_if::cfg_if! { + if #[cfg(engine)] { + use std::thread; + use std::ops::Deref; + } +} + +#[cfg(engine)] +use crate::data::store::DATA; +use crate::data::pool_match::PoolMatchList; + +#[derive(Serialize, Deserialize, ReactiveState, Clone)] +#[rx(alias = "AppStateRx")] +pub struct AppState { + pub matches: PoolMatchList, +} + +pub fn get_global_state_creator() -> GlobalStateCreator { + GlobalStateCreator::new() + .build_state_fn(get_build_state) + .request_state_fn(get_request_state) +} + +#[engine_only_fn] +fn get_state() -> AppState { + let matches = thread::spawn(move || { + DATA.lock().unwrap().deref().matches.clone() + }).join().unwrap(); + + AppState { + matches + } +} + + +#[engine_only_fn] +pub async fn get_build_state() -> AppState { + get_state() +} + +#[engine_only_fn] +pub async fn get_request_state(_req: Request) -> AppState { + get_state() +} diff --git a/src/templates/mod.rs b/src/templates/mod.rs index 19b6c4e..f2368c1 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -2,10 +2,12 @@ pub mod index; pub mod add_game_form; pub mod one_v_one_board; pub mod overall_board; +pub mod global_state; #[cfg(client)] use perseus::utils::get_path_prefix_client; +#[allow(dead_code)] pub fn get_api_path(path: &str) -> String { #[cfg(engine)] { @@ -13,9 +15,8 @@ pub fn get_api_path(path: &str) -> String { } #[cfg(client)] { - let path = web_sys::window().unwrap().location().pathname().unwrap(); - // let base_path = get_path_prefix_client(); - // format!("{}{}", base_path, path) - path.to_string() + let origin = web_sys::window().unwrap().origin(); + let base_path = get_path_prefix_client(); + format!("{}{}{}", origin, base_path, path) } -} \ No newline at end of file +} diff --git a/src/templates/one_v_one_board.rs b/src/templates/one_v_one_board.rs index 787cffb..570e763 100644 --- a/src/templates/one_v_one_board.rs +++ b/src/templates/one_v_one_board.rs @@ -11,7 +11,7 @@ struct PageState { } -fn one_v_one_board_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a PageStateRx) -> View { +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 @@ -23,7 +23,7 @@ fn one_v_one_board_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a PageSt #[engine_only_fn] async fn get_request_state( _info: StateGeneratorInfo<()>, - req: Request, + _req: Request, ) -> Result> { Ok(PageState {}) } diff --git a/src/templates/overall_board.rs b/src/templates/overall_board.rs index f8024cf..c3ab4bb 100644 --- a/src/templates/overall_board.rs +++ b/src/templates/overall_board.rs @@ -1,16 +1,8 @@ use crate::components::layout::Layout; use perseus::prelude::*; use serde::{Deserialize, Serialize}; -#[cfg(engine)] -use crate::data::store::DATA; -#[cfg(engine)] -use std::thread; use sycamore::prelude::*; -use crate::data::global_state::AppStateRx; - -use crate::data::pool_match::{ - PoolMatchList, PoolMatch -}; +use crate::templates::global_state::AppStateRx; // Reactive page @@ -20,7 +12,7 @@ struct PageState { } -fn overall_board_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a PageStateRx) -> View { +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, @@ -28,13 +20,12 @@ fn overall_board_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a PageStat // Anything we put in here will be rendered inside the `
` block of the layout ul { (View::new_fragment( - global_state.store.get() - .matches + global_state.matches.get() .pool_matches .iter() .rev() .enumerate() - .map(|(index, item)| { + .map(|(_index, item)| { let game = item.clone(); view! { cx, li { @@ -52,7 +43,7 @@ fn overall_board_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a PageStat #[engine_only_fn] async fn get_request_state( _info: StateGeneratorInfo<()>, - req: Request, + _req: Request, ) -> Result> { Ok(PageState {}) } -- 2.49.1 From 30f3aa63d5ba4d74ca7e2b75157bcabd976ba299 Mon Sep 17 00:00:00 2001 From: Matthew Kaminski Date: Fri, 22 Sep 2023 03:45:48 -0400 Subject: [PATCH 4/4] 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) -- 2.49.1