diff --git a/Cargo.lock b/Cargo.lock index 1922cd2..84bee04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,6 +169,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "boolinator" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" + [[package]] name = "bson" version = "2.4.0" @@ -242,6 +248,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "cookie" version = "0.16.0" @@ -686,6 +702,115 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "gloo" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23947965eee55e3e97a5cd142dd4c10631cc349b48cecca0ed230fd296f568cd" +dependencies = [ + "gloo-console", + "gloo-dialogs", + "gloo-events", + "gloo-file", + "gloo-render", + "gloo-storage", + "gloo-timers", + "gloo-utils", +] + +[[package]] +name = "gloo-console" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f" +dependencies = [ + "gloo-utils", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-dialogs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67062364ac72d27f08445a46cab428188e2e224ec9e37efdba48ae8c289002e6" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-events" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b107f8abed8105e4182de63845afcc7b69c098b7852a813ea7462a320992fc" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7" +dependencies = [ + "gloo-events", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-render" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd9306aef67cfd4449823aadcd14e3958e0800aa2183955a309112a84ec7764" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480" +dependencies = [ + "gloo-utils", + "js-sys", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40913a05c8297adca04392f707b1e73b12ba7b8eab7244a4961580b1fd34063c" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "h2" version = "0.3.14" @@ -939,11 +1064,12 @@ dependencies = [ ] [[package]] -name = "lan-party" +name = "lan_party_backend" version = "0.1.0" dependencies = [ "dashmap", "futures", + "lan_party_core", "lazy_static", "okapi", "paste", @@ -958,6 +1084,24 @@ dependencies = [ "uuid 1.1.2", ] +[[package]] +name = "lan_party_core" +version = "0.1.0" +dependencies = [ + "paste", + "rocket", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "lan_party_web" +version = "0.1.0" +dependencies = [ + "yew", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1386,6 +1530,30 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.43" @@ -1789,6 +1957,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +[[package]] +name = "scoped-tls-hkt" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63" + [[package]] name = "scopeguard" version = "1.1.0" @@ -2653,6 +2827,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.82" @@ -2858,3 +3044,36 @@ name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "yew" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1ccb53e57d3f7d847338cf5758befa811cabe207df07f543c06f502f9998cd" +dependencies = [ + "console_error_panic_hook", + "gloo", + "gloo-utils", + "indexmap", + "js-sys", + "scoped-tls-hkt", + "slab", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "yew-macro", +] + +[[package]] +name = "yew-macro" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fab79082b556d768d6e21811869c761893f0450e1d550a67892b9bce303b7bb" +dependencies = [ + "boolinator", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 7873229..bf921cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,36 +1,8 @@ -[package] -name = "lan-party" -version = "0.1.0" -edition = "2021" +[workspace] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -rocket = { version = "0.5.0-rc.2", features = ["json"] } -dashmap = "5.3.4" -thiserror = "1.0" -schemars = "0.8.10" -okapi = { version = "0.7.0-rc.1" } -rocket_okapi = { version = "0.8.0-rc.2", features = ["swagger", "rocket_db_pools", "rapidoc"] } -futures = "0.3" -lazy_static = "1.4" -serde_json = "1" -paste = "1" -rocket_cors = { git = "https://github.com/lawliet89/rocket_cors" } - -[dependencies.sqlx] -version = "*" -default-features = false -features = ["macros", "offline", "migrate"] - -[dependencies.rocket_db_pools] -version = "0.1.0-rc.2" -features = ["sqlx_sqlite", "mongodb"] - -[dependencies.uuid] -version = "1.1.2" -features = [ - "v4", # Lets you generate random UUIDs - "fast-rng", # Use a faster (but still sufficiently random) RNG - "macro-diagnostics", # Enable better diagnostics for compile-time UUIDs +members = [ + "backend", + "core", + "web" ] + diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..70ba123 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,3 @@ +/target +api_keys.json +*.sqlite-* diff --git a/backend/Cargo.toml b/backend/Cargo.toml new file mode 100644 index 0000000..9e57a5d --- /dev/null +++ b/backend/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "lan_party_backend" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rocket = { version = "0.5.0-rc.2", features = ["json"] } +dashmap = "5.3.4" +thiserror = "1.0" +schemars = "0.8.10" +okapi = { version = "0.7.0-rc.1" } +rocket_okapi = { version = "0.8.0-rc.2", features = ["swagger", "rocket_db_pools", "rapidoc"] } +futures = "0.3" +lazy_static = "1.4" +serde_json = "1" +paste = "1" +rocket_cors = { git = "https://github.com/lawliet89/rocket_cors" } +lan_party_core = { path = "../core", features = ["serde", "rocket", "openapi"] } + +[dependencies.sqlx] +version = "*" +default-features = false +features = ["macros", "offline", "migrate"] + +[dependencies.rocket_db_pools] +version = "0.1.0-rc.2" +features = ["sqlx_sqlite", "mongodb"] + +[dependencies.uuid] +version = "1.1.2" +features = [ + "v4", # Lets you generate random UUIDs + "fast-rng", # Use a faster (but still sufficiently random) RNG + "macro-diagnostics", # Enable better diagnostics for compile-time UUIDs +] diff --git a/backend/rustfmt.toml b/backend/rustfmt.toml new file mode 100644 index 0000000..bf77223 --- /dev/null +++ b/backend/rustfmt.toml @@ -0,0 +1,2 @@ +imports_granularity = "Crate" +edition = "2021" diff --git a/src/api/auth.rs b/backend/src/api/auth.rs similarity index 100% rename from src/api/auth.rs rename to backend/src/api/auth.rs diff --git a/backend/src/api/event.rs b/backend/src/api/event.rs new file mode 100644 index 0000000..f850169 --- /dev/null +++ b/backend/src/api/event.rs @@ -0,0 +1,166 @@ +use std::future; + +use futures::{StreamExt, TryStreamExt}; +use rocket_db_pools::mongodb::bson::to_bson; + +use super::{prelude::*, util::PartyError}; + +use lan_party_core::event::*; + +api_routes!( + create_event, + stop_event, + get_event, + update_event, + get_all_events, + event_outcome, +); + +pub async fn apply_outcome(outcome: &EventOutcome, db: &Connection) -> Result<(), PartyError> { + for (player, reward) in outcome.points.iter() { + db.users() + .update_one( + doc! { "id": player }, + doc! { "$inc": { "score": reward } }, + None, + ) + .await?; + } + Ok(()) +} + +/// # Create event +/// +/// Returns the created event. +#[openapi(tag = "Event")] +#[post("/", data = "")] +pub async fn create_event( + _api_key: ApiKey, + db: Connection, + event: Json, +) -> Result, PartyError> { + let event = event.into_inner().create_event()?; + db.events().insert_one(&event, None).await?; + Ok(Json(event)) +} + +/// # Update event +/// +/// Update the supplied values in the event with the given name. The `name` of an event cannot be +/// changed and `concluded` will always be false. +#[openapi(tag = "Event")] +#[post("/", data = "")] +pub async fn update_event( + _api_key: ApiKey, + db: Connection, + name: String, + update: Json, +) -> Result { + let mut event = db + .events() + .find_one(doc! { "name": &name }, None) + .await? + .ok_or(PartyError::EventNotFound(name.clone()))?; + + event.apply_update(update.into_inner())?; + + db.events() + .update_one( + doc! { "name": &name }, + doc! { "$set": to_bson(&event)? }, + None, + ) + .await?; + + Ok(Status::Ok) +} + +/// # Get event +/// +/// Returns the event with the given name +#[openapi(tag = "Event")] +#[get("/")] +pub async fn get_event( + _api_key: ApiKey, + db: Connection, + name: String, +) -> Result, PartyError> { + Ok(Json( + db.events() + .find_one(doc! { "name": &name }, None) + .await? + .ok_or(PartyError::EventNotFound(name))?, + )) +} + +/// # Get events +/// +/// Returns all events +#[openapi(tag = "Event")] +#[get("/?")] +pub async fn get_all_events( + _api_key: ApiKey, + db: Connection, + concluded: Option, +) -> Result>, PartyError> { + let filter = if let Some(concluded) = concluded { + doc! { "concluded": concluded } + } else { + doc! {} + }; + Ok(Json( + db.events() + .find(filter, None) + .await? + .filter(|e| future::ready(e.is_ok())) + .try_collect() + .await?, + )) +} + +/// # Get outcome +/// +/// Returns the outcome of an event. +#[openapi(tag = "Event")] +#[get("//outcome")] +pub async fn event_outcome( + _api_key: ApiKey, + db: Connection, + name: String, +) -> Result, PartyError> { + let event = db + .events() + .find_one(doc! { "name": &name }, None) + .await? + .ok_or(PartyError::EventNotFound(name.clone()))?; + Ok(Json(event.outcome())) +} + +/// # Stop event +/// +/// This will conclude the event, apply the outcome to the users and return the outcome. +#[openapi(tag = "Event")] +#[post("//stop")] +pub async fn stop_event( + _api_key: ApiKey, + db: Connection, + name: String, +) -> Result, PartyError> { + let event = db + .events() + .find_one(doc! { "name": &name }, None) + .await? + .ok_or(PartyError::EventNotFound(name.clone()))?; + event.conclude(); + db.events() + .update_one( + doc! { "name": &name }, + doc! { "$set": { "concluded": true } }, + None, + ) + .await?; + let outcome = event.outcome(); + apply_outcome(&outcome, &db).await?; + + Ok(Json(outcome)) +} diff --git a/src/api/mod.rs b/backend/src/api/mod.rs similarity index 100% rename from src/api/mod.rs rename to backend/src/api/mod.rs diff --git a/src/api/user.rs b/backend/src/api/user.rs similarity index 81% rename from src/api/user.rs rename to backend/src/api/user.rs index 0ce7c9f..40967dc 100644 --- a/src/api/user.rs +++ b/backend/src/api/user.rs @@ -1,5 +1,6 @@ use super::prelude::*; use futures::{StreamExt, TryStreamExt}; +use lan_party_core::user::*; use rocket_db_pools::mongodb::options::FindOptions; use util::{Ordering, PartyError}; @@ -12,18 +13,6 @@ api_routes!( get_score ); -/// # User -/// -/// A user that represents a person participating in the LAN party -#[derive(Clone, Debug, FromForm, Serialize, Deserialize, JsonSchema)] -#[serde(crate = "rocket::serde")] -pub struct User { - /// Name of the user - name: String, - /// Score of the user - score: i64, -} - /// # Create new user with the give name /// /// Returns the created user @@ -75,28 +64,6 @@ pub async fn get_user( Ok(Json(user)) } -/// # UserSort -/// -/// Field used to sort users -#[derive(Clone, Debug, FromFormField, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum UserSort { - #[field(value = "score")] - Score, - #[field(value = "name")] - Name, -} - -impl ToString for UserSort { - fn to_string(&self) -> String { - match self { - Self::Score => "score", - Self::Name => "name", - } - .into() - } -} - /// # Get all users /// /// Returns an array of all users sorted by the given sort field and ordering diff --git a/src/api/util.rs b/backend/src/api/util.rs similarity index 85% rename from src/api/util.rs rename to backend/src/api/util.rs index 6b2b76d..9c77929 100644 --- a/src/api/util.rs +++ b/backend/src/api/util.rs @@ -4,7 +4,7 @@ use rocket_okapi::response::OpenApiResponderInner; use schemars::JsonSchema; use thiserror::Error; -use super::{event::Event, user::User}; +use lan_party_core::{event::Event, user::User, util::PartyError as CoreError}; /// # Ordering /// @@ -30,24 +30,16 @@ impl ToString for Ordering { #[derive(Error, Debug)] pub enum PartyError { - #[error("user `{0}` does not exist")] - UserNotFound(String), - #[error("event `{0}` does not exist")] - EventNotFound(String), - #[error("unknown error: {0}")] - Unknown(String), - #[error("invalid parameter: {0}")] - InvalidParameter(String), + #[error("internal error {source:?}")] + CoreError { + #[from] + source: CoreError, + }, #[error("uuid error {source:?}")] UuidError { #[from] source: uuid::Error, }, - #[error("sqlx error {source:?}")] - SqlxError { - #[from] - source: sqlx::Error, - }, #[error("mongodb error {source:?}")] MongodbError { #[from] @@ -58,6 +50,12 @@ pub enum PartyError { #[from] source: mongodb::bson::ser::Error, }, + #[error("user `{0}` does not exist")] + UserNotFound(String), + #[error("event `{0}` does not exist")] + EventNotFound(String), + #[error("unknown error: {0}")] + Unknown(String), } impl OpenApiResponderInner for PartyError { @@ -72,7 +70,10 @@ impl<'r, 'o: 'r> Responder<'r, 'o> for PartyError { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> { println!("{:#?}", &self); match self { - Self::UserNotFound(_) | Self::EventNotFound(_) => Status::NotFound, + Self::CoreError { source } => match source { + CoreError::UserNotFound(_) | CoreError::EventNotFound(_) => Status::NotFound, + _ => Status::InternalServerError, + }, _ => Status::InternalServerError, } .respond_to(req) diff --git a/src/main.rs b/backend/src/main.rs similarity index 100% rename from src/main.rs rename to backend/src/main.rs diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 0000000..70ba123 --- /dev/null +++ b/core/.gitignore @@ -0,0 +1,3 @@ +/target +api_keys.json +*.sqlite-* diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000..295b8cd --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "lan_party_core" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +serde = ["dep:serde"] +openapi = ["dep:schemars"] +rocket = ["dep:rocket", "serde"] + +[dependencies] +schemars = { version = "0.8", optional = true } +serde = { version = "1", optional = true } +rocket = { version = "0.5.0-rc.2", features = ["json"], optional = true } +paste = "1" +thiserror = "1.0" diff --git a/core/rustfmt.toml b/core/rustfmt.toml new file mode 100644 index 0000000..bf77223 --- /dev/null +++ b/core/rustfmt.toml @@ -0,0 +1,2 @@ +imports_granularity = "Crate" +edition = "2021" diff --git a/src/api/event.rs b/core/src/event.rs similarity index 67% rename from src/api/event.rs rename to core/src/event.rs index 2cafb25..ded74b5 100644 --- a/src/api/event.rs +++ b/core/src/event.rs @@ -1,58 +1,36 @@ -use std::{collections::HashMap, future, hash::Hash}; - -use futures::{StreamExt, TryStreamExt}; -use rocket_db_pools::mongodb::bson::to_bson; - -use super::{prelude::*, util::PartyError}; +use crate::util::PartyError; use paste::paste; +#[cfg(feature = "openapi")] +use schemars::JsonSchema; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; -api_routes!( - create_event, - stop_event, - get_event, - update_event, - get_all_events, - event_outcome, -); - -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -#[serde(crate = "rocket::serde")] +#[derive(Clone, Debug, Default)] +#[cfg_attr(feature = "openapi", derive(JsonSchema))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct EventOutcome { - points: HashMap, -} - -impl EventOutcome { - pub async fn apply(&self, db: &Connection) -> Result<(), PartyError> { - for (player, reward) in self.points.iter() { - db.users() - .update_one( - doc! { "id": player }, - doc! { "$inc": { "score": reward } }, - None, - ) - .await?; - } - Ok(()) - } + pub points: HashMap, } /// # Event /// /// An event in which participants can win or lose points -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -#[serde(crate = "rocket::serde")] +#[derive(Clone, Debug)] +#[cfg_attr(feature = "openapi", derive(JsonSchema))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Event { /// Has this event concluded? - #[serde(default)] - concluded: bool, + #[cfg_attr(feature = "serde", serde(default))] + pub concluded: bool, /// Name of the event - #[serde(default)] - name: String, + #[cfg_attr(feature = "serde", serde(default))] + pub name: String, /// Description of the event - #[serde(default)] - description: String, + #[cfg_attr(feature = "serde", serde(default))] + pub description: String, /// Event type - event_type: EventType, + pub event_type: EventType, } impl Event { @@ -70,12 +48,13 @@ impl Event { /// # EventSpec /// /// A specification of an event -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -#[serde(crate = "rocket::serde")] +#[derive(Clone, Debug)] +#[cfg_attr(feature = "openapi", derive(JsonSchema))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct EventSpec { - name: String, - description: String, - event_type: EventTypeSpec, + pub name: String, + pub description: String, + pub event_type: EventTypeSpec, } macro_rules! events { @@ -84,8 +63,9 @@ macro_rules! events { /// # EventTypeSpec /// /// A specification of an event type - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum EventTypeSpec { $($name($module::[<$name Spec>]),)* } @@ -93,8 +73,9 @@ macro_rules! events { /// # EventUpdate /// /// An update that can be applied to an event - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum EventUpdate { $($name($module::[<$name Update>]),)* } @@ -102,8 +83,9 @@ macro_rules! events { /// # EventType /// /// An enumeration of event types - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum EventType { $($name($module::$name),)* } @@ -167,22 +149,25 @@ pub trait EventTrait { mod test { use super::*; - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Test { - num_players: i64, + pub num_players: i64, } - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct TestSpec { - num_players: i64, + pub num_players: i64, } - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct TestUpdate { - win_game: bool, + pub win_game: bool, } impl EventTrait for Test { @@ -220,34 +205,43 @@ mod team_game { *, }; - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct TeamGame { /// Map of teams with a name as key and an array of players as value - teams: HashMap>, + pub teams: HashMap>, - #[serde(flatten)] - ffa_game: FreeForAllGame, + #[cfg_attr(feature = "serde", serde(flatten))] + pub ffa_game: FreeForAllGame, } - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct TeamGameSpec { /// Map of teams with a name as key and an array of players as value - teams: HashMap>, + pub teams: HashMap>, /// Rewards for winning the game (first element for first place, second element for second /// place, etc.) - #[schemars(example = "super::free_for_all_game::example_win_rewards")] - win_rewards: Vec, + #[cfg_attr( + feature = "openapi", + schemars(example = "super::free_for_all_game::example_win_rewards") + )] + pub win_rewards: Vec, /// Rewards for losing the game (first element for last place, second element for second to /// last place, etc.) - #[schemars(example = "super::free_for_all_game::example_lose_rewards")] - lose_rewards: Vec, + #[cfg_attr( + feature = "openapi", + schemars(example = "super::free_for_all_game::example_lose_rewards") + )] + pub lose_rewards: Vec, } - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum TeamGameUpdateInner { /// Add or replace a team with the given name and array of members SetTeam { team: String, members: Vec }, @@ -256,9 +250,10 @@ mod team_game { RemoveTeam(String), } - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] - #[serde(untagged)] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "serde", serde(untagged))] pub enum TeamGameFfaInheritedUpdate { /// Change the ranking and scores Ranking(FreeForAllGameUpdateRanking), @@ -266,9 +261,10 @@ mod team_game { Rewards(FreeForAllGameUpdateRewards), } - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] - #[serde(untagged)] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "serde", serde(untagged))] pub enum TeamGameUpdate { /// Team specific updates Team(TeamGameUpdateInner), @@ -348,8 +344,9 @@ mod free_for_all_game { use super::*; - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum FreeForAllGameRanking { /// Ranking of participants by user name or team name (first element is first place, second element is second /// place, etc.) @@ -367,16 +364,17 @@ mod free_for_all_game { } } - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct FreeForAllGame { /// Ranking of participants by user name or team name (first element is first place, second element is second /// place, etc.) - ranking: Option, + pub ranking: Option, /// Specification of the game - #[serde(flatten)] - spec: FreeForAllGameSpec, + #[cfg_attr(feature = "serde", serde(flatten))] + pub spec: FreeForAllGameSpec, } pub fn example_win_rewards() -> Vec { @@ -387,24 +385,26 @@ mod free_for_all_game { vec![-3, -2, -1] } - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct FreeForAllGameSpec { /// Array of user ids that participate in the game - pub(crate) participants: HashSet, + pub participants: HashSet, /// Rewards for winning the game (first element for first place, second element for second /// place, etc.) - #[schemars(example = "example_win_rewards")] - pub(crate) win_rewards: Vec, + #[cfg_attr(feature = "schemars", schemars(example = "example_win_rewards"))] + pub win_rewards: Vec, /// Rewards for losing the game (first element for last place, second element for second to /// last place, etc.) - #[schemars(example = "example_lose_rewards")] - pub(crate) lose_rewards: Vec, + #[cfg_attr(feature = "schemars", schemars(example = "example_lose_rewards"))] + pub lose_rewards: Vec, } - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum FreeForAllGameUpdateRanking { /// Replace the current ranking with the given ranking SetRanking(FreeForAllGameRanking), @@ -413,8 +413,9 @@ mod free_for_all_game { ScoreDelta(HashMap), } - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum FreeForAllGameUpdateRewards { /// Set rewards for winning the game SetWinRewards(Vec), @@ -423,8 +424,9 @@ mod free_for_all_game { SetLoseRewards(Vec), } - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum FreeForAllGameUpdateParticipants { /// Set list of participants participating in the game SetParticipants(HashSet), @@ -436,9 +438,10 @@ mod free_for_all_game { RemoveParticipant(String), } - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - #[serde(crate = "rocket::serde")] - #[serde(untagged)] + #[derive(Clone, Debug)] + #[cfg_attr(feature = "openapi", derive(JsonSchema))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "serde", serde(untagged))] pub enum FreeForAllGameUpdate { /// Change the ranking and scores Ranking(FreeForAllGameUpdateRanking), @@ -551,139 +554,3 @@ mod free_for_all_game { } events!(test => Test, team_game => TeamGame, free_for_all_game => FreeForAllGame); - -/// # Create event -/// -/// Returns the created event. -#[openapi(tag = "Event")] -#[post("/", data = "")] -pub async fn create_event( - _api_key: ApiKey, - db: Connection, - event: Json, -) -> Result, PartyError> { - let event = event.into_inner().create_event()?; - db.events().insert_one(&event, None).await?; - Ok(Json(event)) -} - -/// # Update event -/// -/// Update the supplied values in the event with the given name. The `name` of an event cannot be -/// changed and `concluded` will always be false. -#[openapi(tag = "Event")] -#[post("/", data = "")] -pub async fn update_event( - _api_key: ApiKey, - db: Connection, - name: String, - update: Json, -) -> Result { - let mut event = db - .events() - .find_one(doc! { "name": &name }, None) - .await? - .ok_or(PartyError::EventNotFound(name.clone()))?; - - event.apply_update(update.into_inner())?; - - db.events() - .update_one( - doc! { "name": &name }, - doc! { "$set": to_bson(&event)? }, - None, - ) - .await?; - - Ok(Status::Ok) -} - -/// # Get event -/// -/// Returns the event with the given name -#[openapi(tag = "Event")] -#[get("/")] -pub async fn get_event( - _api_key: ApiKey, - db: Connection, - name: String, -) -> Result, PartyError> { - Ok(Json( - db.events() - .find_one(doc! { "name": &name }, None) - .await? - .ok_or(PartyError::EventNotFound(name))?, - )) -} - -/// # Get events -/// -/// Returns all events -#[openapi(tag = "Event")] -#[get("/?")] -pub async fn get_all_events( - _api_key: ApiKey, - db: Connection, - concluded: Option, -) -> Result>, PartyError> { - let filter = if let Some(concluded) = concluded { - doc! { "concluded": concluded } - } else { - doc! {} - }; - Ok(Json( - db.events() - .find(filter, None) - .await? - .filter(|e| future::ready(e.is_ok())) - .try_collect() - .await?, - )) -} - -/// # Get outcome -/// -/// Returns the outcome of an event. -#[openapi(tag = "Event")] -#[get("//outcome")] -pub async fn event_outcome( - _api_key: ApiKey, - db: Connection, - name: String, -) -> Result, PartyError> { - let event = db - .events() - .find_one(doc! { "name": &name }, None) - .await? - .ok_or(PartyError::EventNotFound(name.clone()))?; - Ok(Json(event.outcome())) -} - -/// # Stop event -/// -/// This will conclude the event, apply the outcome to the users and return the outcome. -#[openapi(tag = "Event")] -#[post("//stop")] -pub async fn stop_event( - _api_key: ApiKey, - db: Connection, - name: String, -) -> Result, PartyError> { - let event = db - .events() - .find_one(doc! { "name": &name }, None) - .await? - .ok_or(PartyError::EventNotFound(name.clone()))?; - event.conclude(); - db.events() - .update_one( - doc! { "name": &name }, - doc! { "$set": { "concluded": true } }, - None, - ) - .await?; - let outcome = event.outcome(); - outcome.apply(&db).await?; - - Ok(Json(outcome)) -} diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 0000000..ac22b11 --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,5 @@ +pub mod event; +pub mod user; +pub mod util; + +pub use util::PartyError; diff --git a/core/src/user.rs b/core/src/user.rs new file mode 100644 index 0000000..8cc38d4 --- /dev/null +++ b/core/src/user.rs @@ -0,0 +1,44 @@ +#[cfg(feature = "rocket")] +use rocket::FromFormField; +#[cfg(feature = "openapi")] +use schemars::JsonSchema; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// # User +/// +/// A user that represents a person participating in the LAN party +#[derive(Clone, Debug, Default)] +#[cfg_attr(feature = "openapi", derive(JsonSchema))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct User { + /// Name of the user + pub name: String, + /// Score of the user + pub score: i64, +} + +/// # UserSort +/// +/// Field used to sort users +#[derive(Clone, Debug)] +#[cfg_attr(feature = "openapi", derive(JsonSchema))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "rocket", derive(FromFormField))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +pub enum UserSort { + #[cfg_attr(feature = "rocket", field(value = "score"))] + Score, + #[cfg_attr(feature = "rocket", field(value = "name"))] + Name, +} + +impl ToString for UserSort { + fn to_string(&self) -> String { + match self { + Self::Score => "score", + Self::Name => "name", + } + .into() + } +} diff --git a/core/src/util.rs b/core/src/util.rs new file mode 100644 index 0000000..bda1980 --- /dev/null +++ b/core/src/util.rs @@ -0,0 +1,44 @@ +#[cfg(feature = "rocket")] +use rocket::FromFormField; +#[cfg(feature = "openapi")] +use schemars::JsonSchema; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum PartyError { + #[error("user `{0}` does not exist")] + UserNotFound(String), + #[error("event `{0}` does not exist")] + EventNotFound(String), + #[error("unknown error: {0}")] + Unknown(String), + #[error("invalid parameter: {0}")] + InvalidParameter(String), +} + +/// # Ordering +/// +/// Ordering of data in an array, ascending or descending +#[derive(Clone, Debug)] +#[cfg_attr(feature = "openapi", derive(JsonSchema))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "rocket", derive(FromFormField))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +pub enum Ordering { + #[cfg_attr(feature = "serde", field(value = "desc"))] + Desc, + #[cfg_attr(feature = "serde", field(value = "asc"))] + Asc, +} + +impl ToString for Ordering { + fn to_string(&self) -> String { + match self { + Self::Desc => "DESC", + Self::Asc => "ASC", + } + .into() + } +} diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000..70ba123 --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,3 @@ +/target +api_keys.json +*.sqlite-* diff --git a/web/Cargo.toml b/web/Cargo.toml new file mode 100644 index 0000000..4699c86 --- /dev/null +++ b/web/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "lan_party_web" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +yew = "0.19" diff --git a/web/dist/index-1257202110ca2b53.js b/web/dist/index-1257202110ca2b53.js new file mode 100644 index 0000000..e299763 --- /dev/null +++ b/web/dist/index-1257202110ca2b53.js @@ -0,0 +1,531 @@ + +let wasm; + +const heap = new Array(32).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let heap_next = heap.length; + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function isLikeNone(x) { + return x === undefined || x === null; +} + +let cachedFloat64Memory0 = new Float64Array(); + +function getFloat64Memory0() { + if (cachedFloat64Memory0.byteLength === 0) { + cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer); + } + return cachedFloat64Memory0; +} + +let cachedInt32Memory0 = new Int32Array(); + +function getInt32Memory0() { + if (cachedInt32Memory0.byteLength === 0) { + cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); + } + return cachedInt32Memory0; +} + +const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachedUint8Memory0 = new Uint8Array(); + +function getUint8Memory0() { + if (cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +function dropObject(idx) { + if (idx < 36) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +let WASM_VECTOR_LEN = 0; + +const cachedTextEncoder = new TextEncoder('utf-8'); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length); + getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len); + + const mem = getUint8Memory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3); + const view = getUint8Memory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +function makeClosure(arg0, arg1, dtor, f) { + const state = { a: arg0, b: arg1, cnt: 1, dtor }; + const real = (...args) => { + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + try { + return f(state.a, state.b, ...args); + } finally { + if (--state.cnt === 0) { + wasm.__wbindgen_export_2.get(state.dtor)(state.a, state.b); + state.a = 0; + + } + } + }; + real.original = state; + + return real; +} +function __wbg_adapter_18(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hdd7f308d47caeab7(arg0, arg1, addHeapObject(arg2)); +} + +let cachedUint32Memory0 = new Uint32Array(); + +function getUint32Memory0() { + if (cachedUint32Memory0.byteLength === 0) { + cachedUint32Memory0 = new Uint32Array(wasm.memory.buffer); + } + return cachedUint32Memory0; +} + +function getArrayJsValueFromWasm0(ptr, len) { + const mem = getUint32Memory0(); + const slice = mem.subarray(ptr / 4, ptr / 4 + len); + const result = []; + for (let i = 0; i < slice.length; i++) { + result.push(takeObject(slice[i])); + } + return result; +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } +} + +async function load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } +} + +function getImports() { + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_number_get = function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'number' ? obj : undefined; + getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret; + getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret); + }; + imports.wbg.__wbindgen_number_new = function(arg0) { + const ret = arg0; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); + }; + imports.wbg.__wbg_error_09919627ac0992f5 = function(arg0, arg1) { + try { + console.error(getStringFromWasm0(arg0, arg1)); + } finally { + wasm.__wbindgen_free(arg0, arg1); + } + }; + imports.wbg.__wbg_new_693216e109162396 = function() { + const ret = new Error(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_stack_0ddaca5d1abfb52f = function(arg0, arg1) { + const ret = getObject(arg1).stack; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_warn_921059440157e870 = function(arg0, arg1) { + var v0 = getArrayJsValueFromWasm0(arg0, arg1).slice(); + wasm.__wbindgen_free(arg0, arg1 * 4); + console.warn(...v0); + }; + imports.wbg.__wbg_instanceof_Window_42f092928baaee84 = function(arg0) { + const ret = getObject(arg0) instanceof Window; + return ret; + }; + imports.wbg.__wbg_document_15b2e504fb1556d6 = function(arg0) { + const ret = getObject(arg0).document; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_body_5e6efc7a3c1b65f3 = function(arg0) { + const ret = getObject(arg0).body; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_createElement_28fc3740fb11defb = function() { return handleError(function (arg0, arg1, arg2) { + const ret = getObject(arg0).createElement(getStringFromWasm0(arg1, arg2)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_createElementNS_dd6cca2457c8c16c = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + const ret = getObject(arg0).createElementNS(arg1 === 0 ? undefined : getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_createTextNode_2ab1e3ebc34e2641 = function(arg0, arg1, arg2) { + const ret = getObject(arg0).createTextNode(getStringFromWasm0(arg1, arg2)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_parentElement_14138ef2ff0b9c88 = function(arg0) { + const ret = getObject(arg0).parentElement; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_lastChild_2d1fa5efd0e0edcc = function(arg0) { + const ret = getObject(arg0).lastChild; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_setnodeValue_59d46f408f89fd0b = function(arg0, arg1, arg2) { + getObject(arg0).nodeValue = arg1 === 0 ? undefined : getStringFromWasm0(arg1, arg2); + }; + imports.wbg.__wbg_appendChild_d21bac021b5bbfde = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).appendChild(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_insertBefore_26dfd5eb687a3438 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = getObject(arg0).insertBefore(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_removeChild_94b0c126b878241b = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).removeChild(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_instanceof_Element_1714e50f9bda1d15 = function(arg0) { + const ret = getObject(arg0) instanceof Element; + return ret; + }; + imports.wbg.__wbg_namespaceURI_b343a4afa454dd59 = function(arg0, arg1) { + const ret = getObject(arg1).namespaceURI; + var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_removeAttribute_2d6e56b2f03aa57e = function() { return handleError(function (arg0, arg1, arg2) { + getObject(arg0).removeAttribute(getStringFromWasm0(arg1, arg2)); + }, arguments) }; + imports.wbg.__wbg_setAttribute_8cfc462c0dedd03b = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).setAttribute(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + }, arguments) }; + imports.wbg.__wbg_value_eb32f706ae6bfab2 = function(arg0, arg1) { + const ret = getObject(arg1).value; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_setvalue_3dd349be116107ce = function(arg0, arg1, arg2) { + getObject(arg0).value = getStringFromWasm0(arg1, arg2); + }; + imports.wbg.__wbg_setchecked_a450b330df6b3fa5 = function(arg0, arg1) { + getObject(arg0).checked = arg1 !== 0; + }; + imports.wbg.__wbg_value_30770021ca38e0db = function(arg0, arg1) { + const ret = getObject(arg1).value; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_setvalue_7b7950dacc5eb607 = function(arg0, arg1, arg2) { + getObject(arg0).value = getStringFromWasm0(arg1, arg2); + }; + imports.wbg.__wbg_target_68a5c10e2732a79e = function(arg0) { + const ret = getObject(arg0).target; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_cancelBubble_aa216b328c490cb1 = function(arg0) { + const ret = getObject(arg0).cancelBubble; + return ret; + }; + imports.wbg.__wbg_addEventListener_ec92ea1297eefdfc = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3), getObject(arg4)); + }, arguments) }; + imports.wbg.__wbg_newnoargs_971e9a5abe185139 = function(arg0, arg1) { + const ret = new Function(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_call_33d7bcddbbfa394a = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_valueOf_f83bee79f23e7b05 = function(arg0) { + const ret = getObject(arg0).valueOf(); + return ret; + }; + imports.wbg.__wbg_is_43eb2f9708e964a9 = function(arg0, arg1) { + const ret = Object.is(getObject(arg0), getObject(arg1)); + return ret; + }; + imports.wbg.__wbg_new_e6a9fecc2bf26696 = function() { + const ret = new Object(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_globalThis_3348936ac49df00a = function() { return handleError(function () { + const ret = globalThis.globalThis; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_self_fd00a1ef86d1b2ed = function() { return handleError(function () { + const ret = self.self; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_window_6f6e346d8bbd61d7 = function() { return handleError(function () { + const ret = window.window; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_global_67175caf56f55ca9 = function() { return handleError(function () { + const ret = global.global; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_get_72332cd2bc57924c = function() { return handleError(function (arg0, arg1) { + const ret = Reflect.get(getObject(arg0), getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_set_2762e698c2f5b7e0 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2)); + return ret; + }, arguments) }; + imports.wbg.__wbindgen_is_undefined = function(arg0) { + const ret = getObject(arg0) === undefined; + return ret; + }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(getObject(arg1)); + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + imports.wbg.__wbindgen_closure_wrapper1877 = function(arg0, arg1, arg2) { + const ret = makeClosure(arg0, arg1, 97, __wbg_adapter_18); + return addHeapObject(ret); + }; + + return imports; +} + +function initMemory(imports, maybe_memory) { + +} + +function finalizeInit(instance, module) { + wasm = instance.exports; + init.__wbindgen_wasm_module = module; + cachedFloat64Memory0 = new Float64Array(); + cachedInt32Memory0 = new Int32Array(); + cachedUint32Memory0 = new Uint32Array(); + cachedUint8Memory0 = new Uint8Array(); + + wasm.__wbindgen_start(); + return wasm; +} + +function initSync(bytes) { + const imports = getImports(); + + initMemory(imports); + + const module = new WebAssembly.Module(bytes); + const instance = new WebAssembly.Instance(module, imports); + + return finalizeInit(instance, module); +} + +async function init(input) { + if (typeof input === 'undefined') { + input = new URL('index-1257202110ca2b53_bg.wasm', import.meta.url); + } + const imports = getImports(); + + if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { + input = fetch(input); + } + + initMemory(imports); + + const { instance, module } = await load(await input, imports); + + return finalizeInit(instance, module); +} + +export { initSync } +export default init; diff --git a/web/dist/index-1257202110ca2b53_bg.wasm b/web/dist/index-1257202110ca2b53_bg.wasm new file mode 100644 index 0000000..289d945 Binary files /dev/null and b/web/dist/index-1257202110ca2b53_bg.wasm differ diff --git a/web/dist/index.html b/web/dist/index.html new file mode 100644 index 0000000..cc40969 --- /dev/null +++ b/web/dist/index.html @@ -0,0 +1,34 @@ + + + Yew App + + + + + \ No newline at end of file diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..67281ae --- /dev/null +++ b/web/index.html @@ -0,0 +1,7 @@ + + + + + Yew App + + diff --git a/web/rustfmt.toml b/web/rustfmt.toml new file mode 100644 index 0000000..bf77223 --- /dev/null +++ b/web/rustfmt.toml @@ -0,0 +1,2 @@ +imports_granularity = "Crate" +edition = "2021" diff --git a/web/src/main.rs b/web/src/main.rs new file mode 100644 index 0000000..f727896 --- /dev/null +++ b/web/src/main.rs @@ -0,0 +1,44 @@ +use yew::prelude::*; + +enum Msg { + AddOne, +} + +struct Model { + value: i64, +} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self { value: 0 } + } + + fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + match msg { + Msg::AddOne => { + self.value += 1; + // the value has changed so we need to + // re-render for it to appear on the page + true + } + } + } + + fn view(&self, ctx: &Context) -> Html { + // This gives us a component's "`Scope`" which allows us to send messages, etc to the component. + let link = ctx.link(); + html! { +
+ +

{ self.value }

+
+ } + } +} + +fn main() { + yew::start_app::(); +}