feat: add ids to commands and redemptions

This commit is contained in:
Anna 2021-08-20 13:20:53 -04:00
parent bb8a694b49
commit 6257e7e57a
9 changed files with 60 additions and 15 deletions

11
Cargo.lock generated
View File

@ -246,6 +246,7 @@ dependencies = [
"toml", "toml",
"twitch_api2", "twitch_api2",
"url", "url",
"uuid",
"warp", "warp",
] ]
@ -2121,6 +2122,16 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 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]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"

View File

@ -24,6 +24,7 @@ toml = "0.5"
tokio-tungstenite = { version = "0.15", features = ["rustls-tls"] } tokio-tungstenite = { version = "0.15", features = ["rustls-tls"] }
twitch_api2 = { version = "0.6.0-rc.2", features = ["twitch_oauth2", "client", "reqwest_client", "helix", "pubsub"] } twitch_api2 = { version = "0.6.0-rc.2", features = ["twitch_oauth2", "client", "reqwest_client", "helix", "pubsub"] }
url = "2" url = "2"
uuid = { version = "0.8", features = ["v4", "serde"] }
warp = "0.3" warp = "0.3"
[dependencies.tokio] [dependencies.tokio]

View File

@ -1,6 +1,7 @@
use chrono::Duration; use chrono::Duration;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DurationSecondsWithFrac}; use serde_with::{serde_as, DurationSecondsWithFrac};
use uuid::Uuid;
use crate::app::FullUserToken; use crate::app::FullUserToken;
#[derive(Deserialize, Serialize, Default)] #[derive(Deserialize, Serialize, Default)]
@ -15,6 +16,8 @@ pub struct Config {
#[derive(Deserialize, Serialize, Clone)] #[derive(Deserialize, Serialize, Clone)]
pub struct Command { pub struct Command {
#[serde(default = "Uuid::new_v4")]
pub id: Uuid,
pub name: String, pub name: String,
pub aliases: Vec<String>, pub aliases: Vec<String>,
pub cooldowns: Cooldowns, pub cooldowns: Cooldowns,
@ -38,6 +41,8 @@ pub struct Cooldowns {
#[derive(Deserialize, Serialize, Clone)] #[derive(Deserialize, Serialize, Clone)]
pub struct Redemption { pub struct Redemption {
#[serde(default = "Uuid::new_v4")]
pub id: Uuid,
pub name: String, pub name: String,
pub twitch_id: twitch_api2::types::RewardId, pub twitch_id: twitch_api2::types::RewardId,
pub rhai: String, pub rhai: String,

View File

@ -1,5 +1,6 @@
use chrono::Duration; use chrono::Duration;
use warp::{Filter, Reply, filters::BoxedFilter, http::Uri, Rejection}; use warp::{Filter, Reply, filters::BoxedFilter, http::Uri, Rejection};
use uuid::Uuid;
use crate::app::{ use crate::app::{
State, State,
config::{CommandExecutor, Command, Cooldowns}, config::{CommandExecutor, Command, Cooldowns},
@ -54,16 +55,17 @@ fn commands_add_get() -> BoxedFilter<(impl Reply, )> {
fn get_command_from_body(mut form: HashMap<String, String>) -> Result<Command, Rejection> { fn get_command_from_body(mut form: HashMap<String, String>) -> Result<Command, Rejection> {
let form_get = try { let form_get = try {
let id = super::uuid_from_form(&mut form).unwrap_or_else(|| Uuid::nil());
let name = form.remove("name")?; let name = form.remove("name")?;
let aliases = form.remove("aliases")?; let aliases = form.remove("aliases")?;
let cooldown = form.remove("cooldown").and_then(|t| if t.trim().is_empty() { None } else { Some(t) }); 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 gcd = form.remove("gcd").and_then(|t| if t.trim().is_empty() { None } else { Some(t) });
let kind = form.remove("type")?; let kind = form.remove("type")?;
let script = form.remove("executor_data")?; 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, Some(x) => x,
None => return Err(warp::reject::custom(CustomRejection::InvalidForm)), None => return Err(warp::reject::custom(CustomRejection::InvalidForm)),
}; };
@ -83,6 +85,7 @@ fn get_command_from_body(mut form: HashMap<String, String>) -> Result<Command, R
}; };
Ok(Command { Ok(Command {
id,
name, name,
executor, executor,
aliases: aliases.split("\n").map(|x| x.trim().to_string()).filter(|x| !x.is_empty()).collect(), aliases: aliases.split("\n").map(|x| x.trim().to_string()).filter(|x| !x.is_empty()).collect(),
@ -102,10 +105,13 @@ fn commands_add_post(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.and_then(move |form: HashMap<String, String>| { .and_then(move |form: HashMap<String, String>| {
let state = Arc::clone(&state); let state = Arc::clone(&state);
async move { async move {
let command = match get_command_from_body(form) { let mut command = match get_command_from_body(form) {
Ok(c) => c, Ok(c) => c,
Err(e) => return Err(e), Err(e) => return Err(e),
}; };
command.id = Uuid::new_v4();
state.config.write().await.commands.push(command); state.config.write().await.commands.push(command);
Ok(warp::redirect(Uri::from_static("/commands"))) Ok(warp::redirect(Uri::from_static("/commands")))
@ -119,12 +125,12 @@ fn commands_edit_get(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.and(warp::path("edit")) .and(warp::path("edit"))
.and(warp::path::param()) .and(warp::path::param())
.and(warp::path::end()) .and(warp::path::end())
.and_then(move |name: String| { .and_then(move |id: Uuid| {
let state = Arc::clone(&state); let state = Arc::clone(&state);
async move { async move {
match state.config.read().await.commands match state.config.read().await.commands
.iter() .iter()
.find(|command| command.name == name) .find(|command| command.id == id)
.cloned() .cloned()
{ {
Some(command) => Ok(command), Some(command) => Ok(command),
@ -151,9 +157,14 @@ fn commands_edit_post(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
Ok(c) => c, Ok(c) => c,
Err(e) => return Err(e), Err(e) => return Err(e),
}; };
if command.id.is_nil() {
return Err(warp::reject::custom(CustomRejection::InvalidForm));
}
let mut config = state.config.write().await; let mut config = state.config.write().await;
for existing in config.commands.iter_mut() { for existing in config.commands.iter_mut() {
if existing.name == &*command.name { if existing.id == command.id {
*existing = command; *existing = command;
break; break;
} }
@ -169,17 +180,17 @@ fn commands_delete_post(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
warp::path("commands") warp::path("commands")
.and(warp::path("delete")) .and(warp::path("delete"))
.and(warp::path::end()) .and(warp::path::end())
.and(warp::body::content_length_limit(1024 * 5)) .and(warp::body::content_length_limit(1024))
.and(warp::body::form()) .and(warp::body::form())
.and_then(move |mut form: HashMap<String, String>| { .and_then(move |mut form: HashMap<String, String>| {
let state = Arc::clone(&state); let state = Arc::clone(&state);
async move { async move {
let name = match form.remove("name") { let id = match super::uuid_from_form(&mut form) {
Some(n) => n, Some(n) => n,
None => return Err(warp::reject::custom(CustomRejection::InvalidForm)), 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"))) Ok(warp::redirect(Uri::from_static("/commands")))
} }

View File

@ -7,3 +7,11 @@ pub use self::access_token::*;
pub use self::commands::*; pub use self::commands::*;
pub use self::livesplit::*; pub use self::livesplit::*;
pub use self::redemptions::*; pub use self::redemptions::*;
use uuid::Uuid;
use std::collections::HashMap;
pub fn uuid_from_form(form: &mut HashMap<String, String>) -> Option<Uuid> {
form.remove("id")
.and_then(|id| Uuid::parse_str(&id).ok())
}

View File

@ -7,6 +7,7 @@ use warp::{
filters::BoxedFilter, filters::BoxedFilter,
http::Uri, http::Uri,
}; };
use uuid::Uuid;
use crate::app::{ use crate::app::{
State, State,
config::Redemption, config::Redemption,
@ -79,6 +80,7 @@ fn redemptions_add_post(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
}; };
let redemption = Redemption { let redemption = Redemption {
id: Uuid::new_v4(),
name, name,
twitch_id: RewardId::new(twitch_id), twitch_id: RewardId::new(twitch_id),
rhai, rhai,
@ -101,12 +103,12 @@ fn redemptions_delete_post(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.and_then(move |mut form: HashMap<String, String>| { .and_then(move |mut form: HashMap<String, String>| {
let state = Arc::clone(&state); let state = Arc::clone(&state);
async move { async move {
let name = match form.remove("name") { let id = match super::uuid_from_form(&mut form) {
Some(n) => n, Some(n) => n,
None => return Err(warp::reject::custom(CustomRejection::InvalidForm)), 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"))) Ok(warp::redirect(Uri::from_static("/redemptions")))
} }

View File

@ -15,6 +15,12 @@
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
margin-top: 1em; margin-top: 1em;
max-width: 100%;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
} }
</style> </style>
{% endblock %} {% endblock %}
@ -56,9 +62,9 @@
{{ t }} {{ t }}
{%- endmatch -%} {%- endmatch -%}
</code></pre> </code></pre>
<a href="/commands/edit/{{ command.name }}">Edit</a> <a href="/commands/edit/{{ command.id.to_simple() }}">Edit</a>
<form action="/commands/delete" method="post"> <form action="/commands/delete" method="post">
<input type="hidden" name="name" value="{{ command.name }}"/> <input type="hidden" name="id" value="{{ command.id }}"/>
<button type="submit">Delete</button> <button type="submit">Delete</button>
</form> </form>
</div> </div>

View File

@ -6,10 +6,11 @@
<ul class="breadcrumbs"> <ul class="breadcrumbs">
<li><a href="/">Home</a></li> <li><a href="/">Home</a></li>
<li><a href="/commands">Commands</a></li> <li><a href="/commands">Commands</a></li>
<li class="current"><a href="/commands/edit/{{ command.name }}">Edit</a></li> <li class="current"><a href="/commands/edit/{{ command.id.to_simple() }}">Edit</a></li>
</ul> </ul>
<form action="/commands/edit" method="post"> <form action="/commands/edit" method="post">
<input type="hidden" name="id" value="{{ command.id }}"/>
<input type="text" name="name" placeholder="Name" value="{{ command.name }}"/> <input type="text" name="name" placeholder="Name" value="{{ command.name }}"/>
<textarea name="aliases" placeholder="Aliases separated by newlines">{{ command.aliases.join("\n") }}</textarea> <textarea name="aliases" placeholder="Aliases separated by newlines">{{ command.aliases.join("\n") }}</textarea>
<div> <div>

View File

@ -33,7 +33,7 @@
<em>{{ redemption.twitch_id }}</em> <em>{{ redemption.twitch_id }}</em>
<pre><code>{{ redemption.rhai }}</code></pre> <pre><code>{{ redemption.rhai }}</code></pre>
<form action="/redemptions/delete" method="post"> <form action="/redemptions/delete" method="post">
<input type="hidden" name="name" value="{{ redemption.name }}"/> <input type="hidden" name="id" value="{{ redemption.id }}"/>
<button type="submit">Delete</button> <button type="submit">Delete</button>
</form> </form>
</div> </div>