use crate::AppState; use axum::extract::{Path, State}; use axum::http::StatusCode; use axum::response::Json; use domain::{BroadcastPort, ConfigRepository, EventPublisher, WidgetStateReader}; type S = State>; pub async fn receive_webhook( State(state): S, Path(source_id): Path, Json(body): Json, ) -> Result where C: ConfigRepository, C::Error: std::fmt::Debug, E: EventPublisher, E::Error: std::fmt::Debug, W: WidgetStateReader, B: BroadcastPort, B::Error: std::fmt::Debug, { let source = state .config .get_data_source(source_id) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")))? .ok_or((StatusCode::NOT_FOUND, "data source not found".into()))?; if source.source_type != domain::DataSourceType::Webhook { return Err(( StatusCode::BAD_REQUEST, "data source is not a webhook type".into(), )); } let raw = json_to_domain_value(body); let widgets = state .config .list_widgets() .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")))?; let layout = state .config .get_layout() .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")))?; let changed = state .widget_states .apply_raw_data(source_id, &raw, &widgets) .await; if !changed.is_empty() && let Some(l) = &layout { let _ = state.broadcaster.push_screen_update(l, &changed).await; } Ok(StatusCode::OK) } fn json_to_domain_value(json: serde_json::Value) -> domain::Value { match json { serde_json::Value::Null => domain::Value::Null, serde_json::Value::Bool(b) => domain::Value::Bool(b), serde_json::Value::Number(n) => domain::Value::Number(n.as_f64().unwrap_or(0.0)), serde_json::Value::String(s) => domain::Value::String(s), serde_json::Value::Array(arr) => { domain::Value::Array(arr.into_iter().map(json_to_domain_value).collect()) } serde_json::Value::Object(obj) => { let map = obj .into_iter() .map(|(k, v)| (k, json_to_domain_value(v))) .collect(); domain::Value::Object(map) } } }