rewire bootstrap with SQLite, HTTP API, and real polling

bootstrap: SQLite config, HTTP API on :3000, TCP on :2699, poll loops.
http-api: added serve() so bootstrap doesn't depend on axum directly.
polling: reads data sources from config, polls via http-json adapter,
pushes changed widgets to connected clients.

configure via API, e.g.:
  curl -X POST localhost:3000/api/data-sources -H 'Content-Type: application/json' -d '{...}'
  curl -X PUT localhost:3000/api/layout -H 'Content-Type: application/json' -d '{...}'
This commit is contained in:
2026-06-18 23:12:05 +02:00
parent af47e3939c
commit 15b75d860c
9 changed files with 135 additions and 85 deletions

View File

@@ -11,6 +11,7 @@ axum.workspace = true
tower-http.workspace = true
serde.workspace = true
serde_json.workspace = true
tokio.workspace = true
[dev-dependencies]
tokio.workspace = true

View File

@@ -31,3 +31,15 @@ where
.layer(CorsLayer::permissive())
.with_state(state)
}
pub async fn serve<C, E>(addr: &str, state: AppState<C, E>) -> Result<(), std::io::Error>
where
C: ConfigRepository + Send + Sync + 'static,
C::Error: std::fmt::Debug + Send,
E: EventPublisher + Send + Sync + 'static,
E::Error: std::fmt::Debug + Send,
{
let app = router(state);
let listener = tokio::net::TcpListener::bind(addr).await?;
axum::serve(listener, app).await
}

View File

@@ -6,7 +6,7 @@ edition = "2024"
[dependencies]
domain.workspace = true
reqwest.workspace = true
quick-xml = { version = "0.37", features = ["serialize"] }
quick-xml = { version = "0.40", features = ["serialize"] }
serde.workspace = true
thiserror.workspace = true

View File

@@ -37,7 +37,7 @@ pub fn parse_rss(xml: &str) -> Result<Value, RssError> {
current_tag.clear();
}
Ok(Event::Text(e)) => {
let text = e.unescape().unwrap_or_default().to_string();
let text = String::from_utf8_lossy(e.as_ref()).to_string();
if !current_tag.is_empty() && !text.trim().is_empty() {
if let Some(item) = current_item.as_mut() {
item.insert(current_tag.clone(), Value::String(text));