feat: web frontend; middleware; serde (WIP?)
This commit is contained in:
@@ -9,7 +9,7 @@ use axum::{
|
||||
};
|
||||
use http_body_util::BodyExt;
|
||||
use serde_json::Value;
|
||||
use tracing::{info, warn};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use crate::{AppState, crypto};
|
||||
|
||||
@@ -77,24 +77,35 @@ pub async fn log_middleware(
|
||||
};
|
||||
let resp_body_text = String::from_utf8(body_bytes.clone().to_vec()).unwrap_or_default();
|
||||
|
||||
let response_body_to_log = if Some("com.linspirer.device.getcommand") == method.as_deref() {
|
||||
match handle_getcommand_response(&resp_body_text, &state).await {
|
||||
Ok(new_body) => ResponseBody::Modified(new_body),
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Failed to handle getcommand response: {}. Responding with empty command list.",
|
||||
e
|
||||
);
|
||||
let mut empty_response =
|
||||
serde_json::from_str::<Value>(&resp_body_text).unwrap_or(Value::Null);
|
||||
if let Some(obj) = empty_response.as_object_mut() {
|
||||
obj.insert("result".to_string(), Value::Array(vec![]));
|
||||
// Check for generic method interception first
|
||||
let response_body_to_log = if let Some(method_str) = &method {
|
||||
if let Ok(Some(intercepted)) =
|
||||
maybe_intercept_response(method_str, &resp_body_text, &state).await
|
||||
{
|
||||
info!("Intercepting response for method: {}", method_str);
|
||||
ResponseBody::Original(intercepted)
|
||||
} else if Some("com.linspirer.device.getcommand") == method.as_deref() {
|
||||
// Special handling for getcommand
|
||||
match handle_getcommand_response(&resp_body_text, &state).await {
|
||||
Ok(new_body) => ResponseBody::Modified(new_body),
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Failed to handle getcommand response: {}. Responding with empty command list.",
|
||||
e
|
||||
);
|
||||
let mut empty_response =
|
||||
serde_json::from_str::<Value>(&resp_body_text).unwrap_or(Value::Null);
|
||||
if let Some(obj) = empty_response.as_object_mut() {
|
||||
obj.insert("result".to_string(), Value::Array(vec![]));
|
||||
}
|
||||
ResponseBody::Modified(empty_response)
|
||||
}
|
||||
ResponseBody::Modified(empty_response)
|
||||
}
|
||||
} else {
|
||||
ResponseBody::Original(resp_body_text.clone())
|
||||
}
|
||||
} else {
|
||||
ResponseBody::Original(resp_body_text)
|
||||
ResponseBody::Original(resp_body_text.clone())
|
||||
};
|
||||
|
||||
let (decrypted_response_for_log, final_response_body) = match response_body_to_log {
|
||||
@@ -111,7 +122,7 @@ pub async fn log_middleware(
|
||||
}
|
||||
};
|
||||
|
||||
info!(
|
||||
debug!(
|
||||
"{}\nRequest:\n{}\nResponse:\n{}\n{}",
|
||||
path,
|
||||
serde_json::to_string_pretty(&decrypted_request_log).unwrap_or_default(),
|
||||
@@ -149,23 +160,61 @@ fn process_and_log_request(body: &str, key: &str, iv: &str) -> anyhow::Result<Va
|
||||
Ok(request_data)
|
||||
}
|
||||
|
||||
async fn handle_getcommand_response(body_text: &str, state: &Arc<AppState>) -> anyhow::Result<Value> {
|
||||
async fn handle_getcommand_response(
|
||||
body_text: &str,
|
||||
state: &Arc<AppState>,
|
||||
) -> anyhow::Result<Value> {
|
||||
let decrypted = crypto::decrypt(body_text, &state.key, &state.iv)?;
|
||||
let mut response_json: Value = serde_json::from_str(&decrypted)?;
|
||||
|
||||
if let Some(result) = response_json.get("result")
|
||||
&& let Some(commands) = result.as_array()
|
||||
if let Some(result) = response_json.get_mut("result")
|
||||
&& let Some(commands) = result.as_array_mut()
|
||||
&& !commands.is_empty()
|
||||
{
|
||||
let mut queue = state.command_queue.write().await;
|
||||
for cmd in commands {
|
||||
queue.push(cmd.clone());
|
||||
// Persist commands to database
|
||||
for cmd in commands.iter() {
|
||||
let cmd_json = serde_json::to_string(cmd)?;
|
||||
if let Err(e) =
|
||||
crate::db::repositories::commands::insert(&state.db, &cmd_json, "unverified").await
|
||||
{
|
||||
warn!("Failed to persist command to database: {}", e);
|
||||
}
|
||||
info!("Added command to the queue: {:?}", cmd);
|
||||
}
|
||||
|
||||
// Also add to in-memory queue for backwards compatibility
|
||||
let mut queue = state.commands.unverified.write().await;
|
||||
queue.extend(commands.drain(..));
|
||||
}
|
||||
|
||||
if let Some(obj) = response_json.as_object_mut() {
|
||||
obj.insert("result".to_string(), Value::Array(vec![]));
|
||||
// Get verified commands from database
|
||||
let verified_cmds =
|
||||
match crate::db::repositories::commands::list_by_status(&state.db, "verified").await {
|
||||
Ok(cmds) => cmds,
|
||||
Err(e) => {
|
||||
warn!("Failed to fetch verified commands from database: {}", e);
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
// Convert to JSON values
|
||||
let verified_values: Vec<Value> = verified_cmds
|
||||
.iter()
|
||||
.filter_map(|c| serde_json::from_str(&c.command_json).ok())
|
||||
.collect();
|
||||
|
||||
// Also include in-memory verified commands
|
||||
let mem_verified = std::mem::take(&mut *state.commands.verified.write().await);
|
||||
let mut all_verified = verified_values;
|
||||
all_verified.extend(mem_verified);
|
||||
|
||||
obj.insert("result".to_string(), Value::Array(all_verified.clone()));
|
||||
|
||||
// Clear verified commands from database after sending
|
||||
if let Err(e) = crate::db::repositories::commands::clear_verified(&state.db).await {
|
||||
warn!("Failed to clear verified commands from database: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(response_json)
|
||||
@@ -175,4 +224,29 @@ fn decrypt_and_format(body_text: &str, key: &str, iv: &str) -> anyhow::Result<St
|
||||
let decrypted = crypto::decrypt(body_text, key, iv)?;
|
||||
let formatted: Value = serde_json::from_str(&decrypted)?;
|
||||
Ok(serde_json::to_string_pretty(&formatted)?)
|
||||
}
|
||||
}
|
||||
|
||||
async fn maybe_intercept_response(
|
||||
method: &str,
|
||||
_original_response: &str,
|
||||
state: &Arc<AppState>,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
// Check if there's an interception rule for this method
|
||||
let rule = crate::db::repositories::rules::find_by_method(&state.db, method).await?;
|
||||
|
||||
match rule {
|
||||
Some(r) if r.action == "replace" => {
|
||||
if let Some(custom_response) = &r.custom_response {
|
||||
Ok(crypto::encrypt(custom_response, &state.key, &state.iv).map(Some)?)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Some(r) if r.action == "modify" => {
|
||||
// Future: Apply transformations
|
||||
// For now, just pass through
|
||||
Ok(None)
|
||||
}
|
||||
_ => Ok(None), // Passthrough
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user