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.
|
||||
TextBee acceptance means a message was queued, not necessarily delivered.
|
||||
|
||||
Code 2 uses the former manual code value (`Qj4eAn`). On successful submission,
|
||||
it sends a notification to `RECIPIENT_SELF` while the server is live (and logs
|
||||
the notification instead while offline). The result page displays
|
||||
`NEXT_STEP_TEXT` from `src/codes/code2.rs`; replace its TODO text with the next
|
||||
step when ready.
|
||||
Code 2 uses the former manual code value (`QJ4EAN`). Code values are case
|
||||
insensitive. On successful submission, code 2 sends a notification to
|
||||
`RECIPIENT_SELF` while the server is live (and logs the notification instead
|
||||
while offline). The result page displays `NEXT_STEP_TEXT` from
|
||||
`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
|
||||
`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 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_SECOND: &str = "0x144A
|
||||
0x17AE
|
||||
0x17AA
|
||||
0x07A0
|
||||
const CHALLENGE_1_SECOND: &str = "0x164A
|
||||
0x13AA
|
||||
0x13AA
|
||||
0x0580
|
||||
0x079E
|
||||
0x1751
|
||||
0x1351
|
||||
0x02FD
|
||||
0x02FD
|
||||
0x163F
|
||||
0x067A
|
||||
0x062B
|
||||
0x122C
|
||||
0x12FA
|
||||
0x1667
|
||||
0x0606
|
||||
0x179B
|
||||
0x164D
|
||||
0x16F9
|
||||
0x027E
|
||||
0x0629
|
||||
0x03FD
|
||||
0x161F
|
||||
0x047E
|
||||
0x042B
|
||||
0x1E2C
|
||||
0x12F2
|
||||
0x16E7
|
||||
0x0606
|
||||
0x1799
|
||||
0x064C
|
||||
0x1679
|
||||
0x0676
|
||||
0x062B
|
||||
0x062C
|
||||
0x07B3
|
||||
0x12FA
|
||||
0x121F
|
||||
0x0676
|
||||
0x1667
|
||||
0x02FC
|
||||
0x0F9E
|
||||
0x07AD
|
||||
0x061C
|
||||
0x1267
|
||||
0x064D
|
||||
0x16AA
|
||||
0x02F5
|
||||
0x0505
|
||||
0x065B
|
||||
0x1329
|
||||
0x1E2C
|
||||
0x1484
|
||||
0x1671";
|
||||
0x160F
|
||||
0x067E
|
||||
0x0667
|
||||
0x02FD
|
||||
0x0D05
|
||||
0x14F1
|
||||
0x1129
|
||||
0x048E
|
||||
0x1084
|
||||
0x04FF";
|
||||
|
||||
pub async fn buzz_event_handler(
|
||||
ConnectInfo(address): ConnectInfo<SocketAddr>,
|
||||
@@ -140,75 +133,14 @@ async fn on_submit(live: bool, mut delta_ms: i64) -> Result<(), String> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let secrets = crate::secrets::runtime();
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
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],
|
||||
super::send_textbee_sms(&[super::SmsRecipient::SelfRecipient], SELF_NOTIFICATION).await?;
|
||||
super::send_textbee_sms(
|
||||
&[super::SmsRecipient::First, super::SmsRecipient::Second],
|
||||
&congrats_message,
|
||||
)
|
||||
.await?;
|
||||
send_textbee_sms(
|
||||
&client,
|
||||
&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}"));
|
||||
}
|
||||
super::send_textbee_sms(&[super::SmsRecipient::First], CHALLENGE_1_FIRST).await?;
|
||||
super::send_textbee_sms(&[super::SmsRecipient::Second], CHALLENGE_1_SECOND).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+2
-28
@@ -1,5 +1,5 @@
|
||||
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.";
|
||||
|
||||
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(());
|
||||
}
|
||||
|
||||
let secrets = crate::secrets::runtime();
|
||||
|
||||
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(())
|
||||
super::send_textbee_sms(&[super::SmsRecipient::SelfRecipient], SELF_NOTIFICATION).await
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod code2;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod code5;
|
||||
|
||||
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)]
|
||||
struct SubmissionResponse {
|
||||
message: String,
|
||||
@@ -28,27 +80,44 @@ struct DownloadInfo {
|
||||
|
||||
#[server]
|
||||
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);
|
||||
}
|
||||
|
||||
let live = crate::manage::server_live_state();
|
||||
let axum::extract::ConnectInfo(address): axum::extract::ConnectInfo<std::net::SocketAddr> =
|
||||
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 {
|
||||
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(),
|
||||
}),
|
||||
}))
|
||||
if code.eq_ignore_ascii_case(crate::codes::code2::CODE) {
|
||||
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 {
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user