arch: split ConfigRepository, extract polling, consolidate conversions, decouple protocol

- Value↔JSON: From impls on domain Value behind `json` feature, delete 4 duplicate converters
- ConfigRepository split into ConfigRepository (12), UserRepository (3), WidgetStateCache (2)
- polling orchestration moved from bootstrap to application::polling_service
- WidgetRenderer in client-domain owns scroll/cache, both clients use it
- network loop consolidated into client-application::run_connection_loop
- protocol crate drops domain dep, Wire↔Domain conversions move to adapters
This commit is contained in:
2026-06-19 18:12:50 +02:00
parent 1c854d127f
commit 7001b5e911
46 changed files with 1063 additions and 951 deletions

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
domain.workspace = true
domain = { workspace = true, features = ["json"] }
application.workspace = true
api-types.workspace = true
axum.workspace = true

View File

@@ -4,7 +4,7 @@ mod routes;
use axum::Router;
use domain::{
AuthPort, BroadcastPort, ClientRegistry, ConfigRepository, EventPublisher, PasswordHashPort,
WidgetStateReader,
UserRepository, WidgetStateCache, WidgetStateReader,
};
use std::sync::Arc;
use tower_http::cors::CorsLayer;
@@ -38,8 +38,9 @@ impl<C, E, W, B, R, A, H> Clone for AppState<C, E, W, B, R, A, H> {
pub fn router<C, E, W, B, R, A, H>(state: AppState<C, E, W, B, R, A, H>) -> Router
where
C: ConfigRepository + Send + Sync + 'static,
C::Error: std::fmt::Debug + Send,
C: ConfigRepository + UserRepository + WidgetStateCache + Send + Sync + 'static,
<C as ConfigRepository>::Error: std::fmt::Debug + Send,
<C as UserRepository>::Error: std::fmt::Debug + Send,
E: EventPublisher + Send + Sync + 'static,
E::Error: std::fmt::Debug + Send,
W: WidgetStateReader + Send + Sync + 'static,
@@ -69,8 +70,9 @@ pub async fn serve<C, E, W, B, R, A, H>(
state: AppState<C, E, W, B, R, A, H>,
) -> Result<(), std::io::Error>
where
C: ConfigRepository + Send + Sync + 'static,
C::Error: std::fmt::Debug + Send,
C: ConfigRepository + UserRepository + WidgetStateCache + Send + Sync + 'static,
<C as ConfigRepository>::Error: std::fmt::Debug + Send,
<C as UserRepository>::Error: std::fmt::Debug + Send,
E: EventPublisher + Send + Sync + 'static,
E::Error: std::fmt::Debug + Send,
W: WidgetStateReader + Send + Sync + 'static,

View File

@@ -2,7 +2,7 @@ use crate::AppState;
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::Json;
use domain::{AuthPort, ConfigRepository, PasswordHashPort};
use domain::{AuthPort, PasswordHashPort, UserRepository};
use serde::{Deserialize, Serialize};
type S<C, E, W, B, R, A, H> = State<AppState<C, E, W, B, R, A, H>>;
@@ -28,7 +28,7 @@ pub async fn login<C, E, W, B, R, A, H>(
Json(body): Json<LoginRequest>,
) -> Result<Json<LoginResponse>, (StatusCode, String)>
where
C: ConfigRepository,
C: UserRepository,
C::Error: std::fmt::Debug,
A: AuthPort,
H: PasswordHashPort,
@@ -51,7 +51,7 @@ pub async fn register<C, E, W, B, R, A, H>(
Json(body): Json<LoginRequest>,
) -> Result<StatusCode, (StatusCode, String)>
where
C: ConfigRepository,
C: UserRepository,
C::Error: std::fmt::Debug,
H: PasswordHashPort,
{
@@ -71,7 +71,7 @@ pub async fn auth_status<C, E, W, B, R, A, H>(
State(state): S<C, E, W, B, R, A, H>,
) -> Result<Json<StatusResponse>, StatusCode>
where
C: ConfigRepository,
C: UserRepository,
C::Error: std::fmt::Debug,
{
let count = state

View File

@@ -12,13 +12,14 @@ use axum::Router;
use axum::routing::{get, post};
use domain::{
AuthPort, BroadcastPort, ClientRegistry, ConfigRepository, EventPublisher, PasswordHashPort,
WidgetStateReader,
UserRepository, WidgetStateCache, WidgetStateReader,
};
pub fn api_routes<C, E, W, B, R, A, H>() -> Router<AppState<C, E, W, B, R, A, H>>
where
C: ConfigRepository + Send + Sync + 'static,
C::Error: std::fmt::Debug + Send,
C: ConfigRepository + UserRepository + WidgetStateCache + Send + Sync + 'static,
<C as ConfigRepository>::Error: std::fmt::Debug + Send,
<C as UserRepository>::Error: std::fmt::Debug + Send,
E: EventPublisher + Send + Sync + 'static,
E::Error: std::fmt::Debug + Send,
W: WidgetStateReader + Send + Sync + 'static,

View File

@@ -31,7 +31,7 @@ where
));
}
let data = json_to_domain_value(body);
let data: domain::Value = body.into();
state
.events
@@ -41,22 +41,3 @@ where
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)
}
}
}

View File

@@ -126,32 +126,10 @@ where
{
match state.widget_states.get_widget_state(id).await {
Some(ws) => {
let map: serde_json::Map<String, serde_json::Value> = ws
.data
.iter()
.map(|(k, v)| (k.clone(), domain_value_to_json(v)))
.collect();
let map: serde_json::Map<String, serde_json::Value> =
ws.data.iter().map(|(k, v)| (k.clone(), v.into())).collect();
Ok(Json(serde_json::Value::Object(map)))
}
None => Err(StatusCode::NOT_FOUND),
}
}
fn domain_value_to_json(v: &domain::Value) -> serde_json::Value {
match v {
domain::Value::Null => serde_json::Value::Null,
domain::Value::Bool(b) => serde_json::Value::Bool(*b),
domain::Value::Number(n) => serde_json::json!(n),
domain::Value::String(s) => serde_json::Value::String(s.clone()),
domain::Value::Array(arr) => {
serde_json::Value::Array(arr.iter().map(domain_value_to_json).collect())
}
domain::Value::Object(obj) => {
let map = obj
.iter()
.map(|(k, v)| (k.clone(), domain_value_to_json(v)))
.collect();
serde_json::Value::Object(map)
}
}
}