ignore code case. add code 5. refactor send_sms
This commit is contained in:
@@ -62,11 +62,16 @@ recipient 1 and `CHALLENGE_1_SECOND` to recipient 2. The latter two are sent
|
|||||||
literally as written. When offline, the planned messages are logged instead.
|
literally as written. When offline, the planned messages are logged instead.
|
||||||
TextBee acceptance means a message was queued, not necessarily delivered.
|
TextBee acceptance means a message was queued, not necessarily delivered.
|
||||||
|
|
||||||
Code 2 uses the former manual code value (`Qj4eAn`). On successful submission,
|
Code 2 uses the former manual code value (`QJ4EAN`). Code values are case
|
||||||
it sends a notification to `RECIPIENT_SELF` while the server is live (and logs
|
insensitive. On successful submission, code 2 sends a notification to
|
||||||
the notification instead while offline). The result page displays
|
`RECIPIENT_SELF` while the server is live (and logs the notification instead
|
||||||
`NEXT_STEP_TEXT` from `src/codes/code2.rs`; replace its TODO text with the next
|
while offline). The result page displays `NEXT_STEP_TEXT` from
|
||||||
step when ready.
|
`src/codes/code2.rs`; replace its TODO text with the next step when ready.
|
||||||
|
|
||||||
|
Code 5 uses the code value defined in `src/codes/code5.rs`. A successful
|
||||||
|
submission sends a notification to `RECIPIENT_SELF` while the server is live
|
||||||
|
(and logs the notification instead while offline), and displays the completion
|
||||||
|
message without providing a download.
|
||||||
|
|
||||||
The robot submits code 1 with `POST /event/buzz` and a JSON body containing
|
The robot submits code 1 with `POST /event/buzz` and a JSON body containing
|
||||||
`delay_ms` (a non-negative integer) and `secret` (the value of `CODE1_SECRET`).
|
`delay_ms` (a non-negative integer) and `secret` (the value of `CODE1_SECRET`).
|
||||||
|
|||||||
+33
-101
@@ -10,46 +10,39 @@ const ACCEPTANCE_START_SECONDS: u32 = 11 * 60 * 60;
|
|||||||
const ACCEPTANCE_END_SECONDS: u32 = 13 * 60 * 60 + 30 * 60;
|
const ACCEPTANCE_END_SECONDS: u32 = 13 * 60 * 60 + 30 * 60;
|
||||||
const SELF_NOTIFICATION: &str = "Code 1 has been submitted while the server is live.";
|
const SELF_NOTIFICATION: &str = "Code 1 has been submitted while the server is live.";
|
||||||
const CHALLENGE_1_FIRST: &str = "SECDED-13\nPARITY MSB...LSB";
|
const CHALLENGE_1_FIRST: &str = "SECDED-13\nPARITY MSB...LSB";
|
||||||
const CHALLENGE_1_SECOND: &str = "0x144A
|
const CHALLENGE_1_SECOND: &str = "0x164A
|
||||||
0x17AE
|
0x13AA
|
||||||
0x17AA
|
0x13AA
|
||||||
0x07A0
|
0x0580
|
||||||
0x079E
|
0x079E
|
||||||
0x1751
|
0x1351
|
||||||
0x02FD
|
0x02FD
|
||||||
0x02FD
|
0x03FD
|
||||||
0x163F
|
0x161F
|
||||||
0x067A
|
0x047E
|
||||||
0x062B
|
0x042B
|
||||||
0x122C
|
|
||||||
0x12FA
|
|
||||||
0x1667
|
|
||||||
0x0606
|
|
||||||
0x179B
|
|
||||||
0x164D
|
|
||||||
0x16F9
|
|
||||||
0x027E
|
|
||||||
0x0629
|
|
||||||
0x1E2C
|
0x1E2C
|
||||||
|
0x12F2
|
||||||
|
0x16E7
|
||||||
|
0x0606
|
||||||
|
0x1799
|
||||||
|
0x064C
|
||||||
|
0x1679
|
||||||
|
0x0676
|
||||||
|
0x062B
|
||||||
|
0x062C
|
||||||
0x07B3
|
0x07B3
|
||||||
0x12FA
|
0x12FA
|
||||||
0x121F
|
0x160F
|
||||||
0x0676
|
0x067E
|
||||||
0x1667
|
0x0667
|
||||||
0x02FC
|
0x02FD
|
||||||
0x0F9E
|
0x0D05
|
||||||
0x07AD
|
0x14F1
|
||||||
0x061C
|
0x1129
|
||||||
0x1267
|
0x048E
|
||||||
0x064D
|
0x1084
|
||||||
0x16AA
|
0x04FF";
|
||||||
0x02F5
|
|
||||||
0x0505
|
|
||||||
0x065B
|
|
||||||
0x1329
|
|
||||||
0x1E2C
|
|
||||||
0x1484
|
|
||||||
0x1671";
|
|
||||||
|
|
||||||
pub async fn buzz_event_handler(
|
pub async fn buzz_event_handler(
|
||||||
ConnectInfo(address): ConnectInfo<SocketAddr>,
|
ConnectInfo(address): ConnectInfo<SocketAddr>,
|
||||||
@@ -140,75 +133,14 @@ async fn on_submit(live: bool, mut delta_ms: i64) -> Result<(), String> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let secrets = crate::secrets::runtime();
|
super::send_textbee_sms(&[super::SmsRecipient::SelfRecipient], SELF_NOTIFICATION).await?;
|
||||||
let client = reqwest::Client::new();
|
super::send_textbee_sms(
|
||||||
|
&[super::SmsRecipient::First, super::SmsRecipient::Second],
|
||||||
send_textbee_sms(
|
|
||||||
&client,
|
|
||||||
&secrets.textbee_api_key,
|
|
||||||
secrets.textbee_device_id.as_deref(),
|
|
||||||
&[&secrets.recipient_self],
|
|
||||||
SELF_NOTIFICATION,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
send_textbee_sms(
|
|
||||||
&client,
|
|
||||||
&secrets.textbee_api_key,
|
|
||||||
secrets.textbee_device_id.as_deref(),
|
|
||||||
&[&secrets.recipient_1, &secrets.recipient_2],
|
|
||||||
&congrats_message,
|
&congrats_message,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
send_textbee_sms(
|
super::send_textbee_sms(&[super::SmsRecipient::First], CHALLENGE_1_FIRST).await?;
|
||||||
&client,
|
super::send_textbee_sms(&[super::SmsRecipient::Second], CHALLENGE_1_SECOND).await?;
|
||||||
&secrets.textbee_api_key,
|
|
||||||
secrets.textbee_device_id.as_deref(),
|
|
||||||
&[&secrets.recipient_1],
|
|
||||||
CHALLENGE_1_FIRST,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
send_textbee_sms(
|
|
||||||
&client,
|
|
||||||
&secrets.textbee_api_key,
|
|
||||||
secrets.textbee_device_id.as_deref(),
|
|
||||||
&[&secrets.recipient_2],
|
|
||||||
CHALLENGE_1_SECOND,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_textbee_sms(
|
|
||||||
client: &reqwest::Client,
|
|
||||||
api_key: &str,
|
|
||||||
device_id: Option<&str>,
|
|
||||||
recipients: &[&str],
|
|
||||||
message: &str,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let mut payload = serde_json::json!({
|
|
||||||
"message": message,
|
|
||||||
"recipients": recipients,
|
|
||||||
});
|
|
||||||
if let Some(device_id) = device_id {
|
|
||||||
payload["deviceId"] = serde_json::Value::String(device_id.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = client
|
|
||||||
.post("https://api.textbee.dev/api/v1/gateway/send-sms")
|
|
||||||
.header("x-api-key", api_key)
|
|
||||||
.json(&payload)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map_err(|error| format!("TextBee request failed: {error}"))?;
|
|
||||||
|
|
||||||
let status = response.status();
|
|
||||||
if !status.is_success() {
|
|
||||||
let response_body = response.text().await.map_err(|error| {
|
|
||||||
format!("TextBee returned HTTP {status}; unable to read error: {error}")
|
|
||||||
})?;
|
|
||||||
return Err(format!("TextBee returned HTTP {status}: {response_body}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-28
@@ -1,5 +1,5 @@
|
|||||||
pub const ID: &str = "code2";
|
pub const ID: &str = "code2";
|
||||||
pub const CODE: &str = "Qj4eAn";
|
pub const CODE: &str = "QJ4EAN";
|
||||||
pub const SUCCESS_MESSAGE: &str = "Congratulations on completing step 2.";
|
pub const SUCCESS_MESSAGE: &str = "Congratulations on completing step 2.";
|
||||||
|
|
||||||
const SELF_NOTIFICATION: &str = "Code 2 has been submitted.";
|
const SELF_NOTIFICATION: &str = "Code 2 has been submitted.";
|
||||||
@@ -10,31 +10,5 @@ pub(super) async fn on_submit(live: bool) -> Result<(), String> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let secrets = crate::secrets::runtime();
|
super::send_textbee_sms(&[super::SmsRecipient::SelfRecipient], SELF_NOTIFICATION).await
|
||||||
|
|
||||||
let mut payload = serde_json::json!({
|
|
||||||
"message": SELF_NOTIFICATION,
|
|
||||||
"recipients": [&secrets.recipient_self],
|
|
||||||
});
|
|
||||||
if let Some(device_id) = &secrets.textbee_device_id {
|
|
||||||
payload["deviceId"] = serde_json::Value::String(device_id.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = reqwest::Client::new()
|
|
||||||
.post("https://api.textbee.dev/api/v1/gateway/send-sms")
|
|
||||||
.header("x-api-key", &secrets.textbee_api_key)
|
|
||||||
.json(&payload)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map_err(|error| format!("TextBee request failed: {error}"))?;
|
|
||||||
|
|
||||||
let status = response.status();
|
|
||||||
if !status.is_success() {
|
|
||||||
let response_body = response.text().await.map_err(|error| {
|
|
||||||
format!("TextBee returned HTTP {status}; unable to read error: {error}")
|
|
||||||
})?;
|
|
||||||
return Err(format!("TextBee returned HTTP {status}: {response_body}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
pub const ID: &str = "code5";
|
||||||
|
pub const CODE: &str = "V7MK2P";
|
||||||
|
pub const SUCCESS_MESSAGE: &str =
|
||||||
|
"Congradulations on completing challenge 5. We will be in touch";
|
||||||
|
|
||||||
|
const SELF_NOTIFICATION: &str = "Code 5 has been submitted.";
|
||||||
|
|
||||||
|
pub(super) async fn on_submit(live: bool) -> Result<(), String> {
|
||||||
|
if !live {
|
||||||
|
leptos::logging::log!("Code 5 SMS (not sent; server is offline): {SELF_NOTIFICATION}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
super::send_textbee_sms(&[super::SmsRecipient::SelfRecipient], SELF_NOTIFICATION).await
|
||||||
|
}
|
||||||
+83
-14
@@ -9,9 +9,61 @@ use leptos_router::{
|
|||||||
pub mod code1;
|
pub mod code1;
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub mod code2;
|
pub mod code2;
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
pub mod code5;
|
||||||
|
|
||||||
pub mod history;
|
pub mod history;
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub(super) enum SmsRecipient {
|
||||||
|
SelfRecipient,
|
||||||
|
First,
|
||||||
|
Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
pub(super) async fn send_textbee_sms(
|
||||||
|
recipients: &[SmsRecipient],
|
||||||
|
message: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let secrets = crate::secrets::runtime();
|
||||||
|
let recipients: Vec<&str> = recipients
|
||||||
|
.iter()
|
||||||
|
.map(|recipient| match recipient {
|
||||||
|
SmsRecipient::SelfRecipient => secrets.recipient_self.as_str(),
|
||||||
|
SmsRecipient::First => secrets.recipient_1.as_str(),
|
||||||
|
SmsRecipient::Second => secrets.recipient_2.as_str(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut payload = serde_json::json!({
|
||||||
|
"message": message,
|
||||||
|
"recipients": recipients,
|
||||||
|
});
|
||||||
|
if let Some(device_id) = &secrets.textbee_device_id {
|
||||||
|
payload["deviceId"] = serde_json::Value::String(device_id.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = reqwest::Client::new()
|
||||||
|
.post("https://api.textbee.dev/api/v1/gateway/send-sms")
|
||||||
|
.header("x-api-key", &secrets.textbee_api_key)
|
||||||
|
.json(&payload)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|error| format!("TextBee request failed: {error}"))?;
|
||||||
|
|
||||||
|
let status = response.status();
|
||||||
|
if !status.is_success() {
|
||||||
|
let response_body = response.text().await.map_err(|error| {
|
||||||
|
format!("TextBee returned HTTP {status}; unable to read error: {error}")
|
||||||
|
})?;
|
||||||
|
return Err(format!("TextBee returned HTTP {status}: {response_body}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, serde::Deserialize, serde::Serialize)]
|
||||||
struct SubmissionResponse {
|
struct SubmissionResponse {
|
||||||
message: String,
|
message: String,
|
||||||
@@ -28,27 +80,44 @@ struct DownloadInfo {
|
|||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
async fn submit_code(code: String) -> Result<Option<SubmissionResponse>, ServerFnError> {
|
async fn submit_code(code: String) -> Result<Option<SubmissionResponse>, ServerFnError> {
|
||||||
if code != crate::codes::code2::CODE {
|
if !code.eq_ignore_ascii_case(crate::codes::code2::CODE)
|
||||||
|
&& !code.eq_ignore_ascii_case(crate::codes::code5::CODE)
|
||||||
|
{
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let live = crate::manage::server_live_state();
|
let live = crate::manage::server_live_state();
|
||||||
let axum::extract::ConnectInfo(address): axum::extract::ConnectInfo<std::net::SocketAddr> =
|
let axum::extract::ConnectInfo(address): axum::extract::ConnectInfo<std::net::SocketAddr> =
|
||||||
leptos_axum::extract().await?;
|
leptos_axum::extract().await?;
|
||||||
history::record_successful_submission(address.ip(), live, crate::codes::code2::ID)?;
|
|
||||||
crate::codes::code2::on_submit(live)
|
|
||||||
.await
|
|
||||||
.map_err(ServerFnError::new)?;
|
|
||||||
|
|
||||||
Ok(Some(SubmissionResponse {
|
if code.eq_ignore_ascii_case(crate::codes::code2::CODE) {
|
||||||
message: crate::codes::code2::SUCCESS_MESSAGE.to_string(),
|
history::record_successful_submission(address.ip(), live, crate::codes::code2::ID)?;
|
||||||
download: Some(DownloadInfo {
|
crate::codes::code2::on_submit(live)
|
||||||
href: "/challenge3.txt".to_string(),
|
.await
|
||||||
filename: "challenge3.txt".to_string(),
|
.map_err(ServerFnError::new)?;
|
||||||
title: "Download challenge 3".to_string(),
|
|
||||||
description: "TXT file".to_string(),
|
Ok(Some(SubmissionResponse {
|
||||||
}),
|
message: crate::codes::code2::SUCCESS_MESSAGE.to_string(),
|
||||||
}))
|
download: Some(DownloadInfo {
|
||||||
|
href: "/challenge3.txt".to_string(),
|
||||||
|
filename: "challenge3.txt".to_string(),
|
||||||
|
title: "Download challenge 3".to_string(),
|
||||||
|
description: "TXT file".to_string(),
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
} else if code.eq_ignore_ascii_case(crate::codes::code5::CODE) {
|
||||||
|
history::record_successful_submission(address.ip(), live, crate::codes::code5::ID)?;
|
||||||
|
crate::codes::code5::on_submit(live)
|
||||||
|
.await
|
||||||
|
.map_err(ServerFnError::new)?;
|
||||||
|
|
||||||
|
Ok(Some(SubmissionResponse {
|
||||||
|
message: crate::codes::code5::SUCCESS_MESSAGE.to_string(),
|
||||||
|
download: None,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_path_segment(value: &str) -> String {
|
fn encode_path_segment(value: &str) -> String {
|
||||||
|
|||||||
Reference in New Issue
Block a user