webhook through event system, extract data-generators adapter
webhook route now emits WebhookDataReceived event instead of directly mutating DataProjection and broadcasting. event_handler applies data and pushes incremental DataUpdate. clock/static_text generators extracted to data-generators crate behind DataSourcePort. chrono removed from bootstrap. polling adapters grouped into Adapters struct.
This commit is contained in:
10
crates/adapters/data-generators/Cargo.toml
Normal file
10
crates/adapters/data-generators/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "data-generators"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
domain.workspace = true
|
||||
chrono = "0.4"
|
||||
chrono-tz = "0.10"
|
||||
thiserror.workspace = true
|
||||
59
crates/adapters/data-generators/src/lib.rs
Normal file
59
crates/adapters/data-generators/src/lib.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use chrono::Utc;
|
||||
use chrono_tz::Tz;
|
||||
use domain::{DataSource, DataSourceConfig, DataSourcePort, Value};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ClockGenerator;
|
||||
|
||||
impl ClockGenerator {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum GeneratorError {
|
||||
#[error("wrong config type for generator")]
|
||||
WrongConfig,
|
||||
}
|
||||
|
||||
impl DataSourcePort for ClockGenerator {
|
||||
type Error = GeneratorError;
|
||||
|
||||
async fn poll(&self, source: &DataSource) -> Result<Value, Self::Error> {
|
||||
let (fmt, tz_name) = match &source.config {
|
||||
DataSourceConfig::Clock { format, timezone } => (format.as_str(), timezone.as_str()),
|
||||
_ => ("%H:%M:%S", "UTC"),
|
||||
};
|
||||
let tz: Tz = tz_name.parse().unwrap_or(chrono_tz::UTC);
|
||||
let now = Utc::now().with_timezone(&tz);
|
||||
let formatted = now.format(fmt).to_string();
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert("time".into(), Value::String(formatted));
|
||||
Ok(Value::Object(map))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct StaticTextGenerator;
|
||||
|
||||
impl StaticTextGenerator {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl DataSourcePort for StaticTextGenerator {
|
||||
type Error = GeneratorError;
|
||||
|
||||
async fn poll(&self, source: &DataSource) -> Result<Value, Self::Error> {
|
||||
let text = match &source.config {
|
||||
DataSourceConfig::StaticText { text } => text.clone(),
|
||||
_ => String::new(),
|
||||
};
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert("text".into(), Value::String(text));
|
||||
Ok(Value::Object(map))
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use crate::AppState;
|
||||
use axum::extract::{Path, State};
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::Json;
|
||||
use domain::{BroadcastPort, ConfigRepository, EventPublisher, WidgetStateReader};
|
||||
use domain::{ConfigRepository, DomainEvent, EventPublisher};
|
||||
|
||||
type S<C, E, W, B, R, A, H> = State<AppState<C, E, W, B, R, A, H>>;
|
||||
|
||||
@@ -16,9 +16,6 @@ where
|
||||
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
|
||||
@@ -34,37 +31,14 @@ where
|
||||
));
|
||||
}
|
||||
|
||||
let raw = json_to_domain_value(body);
|
||||
let widgets = state
|
||||
.config
|
||||
.list_widgets()
|
||||
let data = json_to_domain_value(body);
|
||||
|
||||
state
|
||||
.events
|
||||
.publish(DomainEvent::WebhookDataReceived { source_id, data })
|
||||
.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 with_hints: Vec<_> = changed
|
||||
.iter()
|
||||
.filter_map(|(id, s)| {
|
||||
let hint = widgets.iter().find(|w| w.id == *id)?.display_hint.clone();
|
||||
Some((*id, hint, s.clone()))
|
||||
})
|
||||
.collect();
|
||||
let _ = state.broadcaster.push_screen_update(l, &with_hints).await;
|
||||
}
|
||||
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user