diff --git a/Cargo.lock b/Cargo.lock index a14c3cd..4dddcef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,6 +246,7 @@ dependencies = [ "toml", "twitch_api2", "url", + "uuid", "warp", ] @@ -2121,6 +2122,16 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.3", + "serde", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index fdc294b..abd380d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ toml = "0.5" tokio-tungstenite = { version = "0.15", features = ["rustls-tls"] } twitch_api2 = { version = "0.6.0-rc.2", features = ["twitch_oauth2", "client", "reqwest_client", "helix", "pubsub"] } url = "2" +uuid = { version = "0.8", features = ["v4", "serde"] } warp = "0.3" [dependencies.tokio] diff --git a/src/app/config.rs b/src/app/config.rs index e32cb73..8c914a9 100644 --- a/src/app/config.rs +++ b/src/app/config.rs @@ -1,6 +1,7 @@ use chrono::Duration; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DurationSecondsWithFrac}; +use uuid::Uuid; use crate::app::FullUserToken; #[derive(Deserialize, Serialize, Default)] @@ -15,6 +16,8 @@ pub struct Config { #[derive(Deserialize, Serialize, Clone)] pub struct Command { + #[serde(default = "Uuid::new_v4")] + pub id: Uuid, pub name: String, pub aliases: Vec, pub cooldowns: Cooldowns, @@ -38,6 +41,8 @@ pub struct Cooldowns { #[derive(Deserialize, Serialize, Clone)] pub struct Redemption { + #[serde(default = "Uuid::new_v4")] + pub id: Uuid, pub name: String, pub twitch_id: twitch_api2::types::RewardId, pub rhai: String, diff --git a/src/app/web/route/commands.rs b/src/app/web/route/commands.rs index e789502..8e88602 100644 --- a/src/app/web/route/commands.rs +++ b/src/app/web/route/commands.rs @@ -1,5 +1,6 @@ use chrono::Duration; use warp::{Filter, Reply, filters::BoxedFilter, http::Uri, Rejection}; +use uuid::Uuid; use crate::app::{ State, config::{CommandExecutor, Command, Cooldowns}, @@ -54,16 +55,17 @@ fn commands_add_get() -> BoxedFilter<(impl Reply, )> { fn get_command_from_body(mut form: HashMap) -> Result { let form_get = try { + let id = super::uuid_from_form(&mut form).unwrap_or_else(|| Uuid::nil()); let name = form.remove("name")?; let aliases = form.remove("aliases")?; let cooldown = form.remove("cooldown").and_then(|t| if t.trim().is_empty() { None } else { Some(t) }); let gcd = form.remove("gcd").and_then(|t| if t.trim().is_empty() { None } else { Some(t) }); let kind = form.remove("type")?; let script = form.remove("executor_data")?; - (name, aliases, cooldown, gcd, kind, script) + (id, name, aliases, cooldown, gcd, kind, script) }; - let (name, aliases, cooldown, gcd, kind, script) = match form_get { + let (id, name, aliases, cooldown, gcd, kind, script) = match form_get { Some(x) => x, None => return Err(warp::reject::custom(CustomRejection::InvalidForm)), }; @@ -83,6 +85,7 @@ fn get_command_from_body(mut form: HashMap) -> Result) -> BoxedFilter<(impl Reply, )> { .and_then(move |form: HashMap| { let state = Arc::clone(&state); async move { - let command = match get_command_from_body(form) { + let mut command = match get_command_from_body(form) { Ok(c) => c, Err(e) => return Err(e), }; + + command.id = Uuid::new_v4(); + state.config.write().await.commands.push(command); Ok(warp::redirect(Uri::from_static("/commands"))) @@ -119,12 +125,12 @@ fn commands_edit_get(state: Arc) -> BoxedFilter<(impl Reply, )> { .and(warp::path("edit")) .and(warp::path::param()) .and(warp::path::end()) - .and_then(move |name: String| { + .and_then(move |id: Uuid| { let state = Arc::clone(&state); async move { match state.config.read().await.commands .iter() - .find(|command| command.name == name) + .find(|command| command.id == id) .cloned() { Some(command) => Ok(command), @@ -151,9 +157,14 @@ fn commands_edit_post(state: Arc) -> BoxedFilter<(impl Reply, )> { Ok(c) => c, Err(e) => return Err(e), }; + + if command.id.is_nil() { + return Err(warp::reject::custom(CustomRejection::InvalidForm)); + } + let mut config = state.config.write().await; for existing in config.commands.iter_mut() { - if existing.name == &*command.name { + if existing.id == command.id { *existing = command; break; } @@ -169,17 +180,17 @@ fn commands_delete_post(state: Arc) -> BoxedFilter<(impl Reply, )> { warp::path("commands") .and(warp::path("delete")) .and(warp::path::end()) - .and(warp::body::content_length_limit(1024 * 5)) + .and(warp::body::content_length_limit(1024)) .and(warp::body::form()) .and_then(move |mut form: HashMap| { let state = Arc::clone(&state); async move { - let name = match form.remove("name") { + let id = match super::uuid_from_form(&mut form) { Some(n) => n, None => return Err(warp::reject::custom(CustomRejection::InvalidForm)), }; - state.config.write().await.commands.drain_filter(|command| command.name == name); + state.config.write().await.commands.drain_filter(|command| command.id == id); Ok(warp::redirect(Uri::from_static("/commands"))) } diff --git a/src/app/web/route/mod.rs b/src/app/web/route/mod.rs index 9a9063a..0c95539 100644 --- a/src/app/web/route/mod.rs +++ b/src/app/web/route/mod.rs @@ -7,3 +7,11 @@ pub use self::access_token::*; pub use self::commands::*; pub use self::livesplit::*; pub use self::redemptions::*; + +use uuid::Uuid; +use std::collections::HashMap; + +pub fn uuid_from_form(form: &mut HashMap) -> Option { + form.remove("id") + .and_then(|id| Uuid::parse_str(&id).ok()) +} diff --git a/src/app/web/route/redemptions.rs b/src/app/web/route/redemptions.rs index f554d86..158e08a 100644 --- a/src/app/web/route/redemptions.rs +++ b/src/app/web/route/redemptions.rs @@ -7,6 +7,7 @@ use warp::{ filters::BoxedFilter, http::Uri, }; +use uuid::Uuid; use crate::app::{ State, config::Redemption, @@ -79,6 +80,7 @@ fn redemptions_add_post(state: Arc) -> BoxedFilter<(impl Reply, )> { }; let redemption = Redemption { + id: Uuid::new_v4(), name, twitch_id: RewardId::new(twitch_id), rhai, @@ -101,12 +103,12 @@ fn redemptions_delete_post(state: Arc) -> BoxedFilter<(impl Reply, )> { .and_then(move |mut form: HashMap| { let state = Arc::clone(&state); async move { - let name = match form.remove("name") { + let id = match super::uuid_from_form(&mut form) { Some(n) => n, None => return Err(warp::reject::custom(CustomRejection::InvalidForm)), }; - state.config.write().await.redemptions.drain_filter(|redemption| redemption.name == name); + state.config.write().await.redemptions.drain_filter(|redemption| redemption.id == id); Ok(warp::redirect(Uri::from_static("/redemptions"))) } diff --git a/templates/commands.html b/templates/commands.html index 67b48d8..37d802e 100644 --- a/templates/commands.html +++ b/templates/commands.html @@ -15,6 +15,12 @@ flex-direction: column; align-items: flex-start; margin-top: 1em; + max-width: 100%; + } + + pre { + white-space: pre-wrap; + word-wrap: break-word; } {% endblock %} @@ -56,9 +62,9 @@ {{ t }} {%- endmatch -%} - Edit + Edit
- +
diff --git a/templates/edit_command.html b/templates/edit_command.html index 6af0cf7..de7f9bb 100644 --- a/templates/edit_command.html +++ b/templates/edit_command.html @@ -6,10 +6,11 @@
+
diff --git a/templates/redemptions.html b/templates/redemptions.html index 9449656..f23d044 100644 --- a/templates/redemptions.html +++ b/templates/redemptions.html @@ -33,7 +33,7 @@ {{ redemption.twitch_id }}
{{ redemption.rhai }}
- +