feat(sync): add typed presence projection
req: sync/001 req: sync/005
This commit is contained in:
@@ -15,7 +15,7 @@ use hemx_axum::{
|
||||
use hemx_kanban_example::ui::board::{self as board};
|
||||
use hemx_kanban_example::ui::board_card as card_board;
|
||||
use hemx_kanban_example::ui::{self, board as board_ui};
|
||||
use hemx_sync::{Channel, SyncEffect};
|
||||
use hemx_sync::{Channel, PresenceScope, PresenceTracker, PresenceUpdate};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::Infallible;
|
||||
@@ -46,6 +46,7 @@ struct AppState {
|
||||
sync_store: Option<SyncStore>,
|
||||
sync_sessions: SyncSessionTokens,
|
||||
acknowledgement_heartbeat_interval: Duration,
|
||||
presence: Mutex<PresenceTracker<String>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -494,6 +495,7 @@ async fn main() {
|
||||
.filter(|milliseconds| *milliseconds > 0)
|
||||
.map(Duration::from_millis)
|
||||
.unwrap_or(ACKNOWLEDGEMENT_HEARTBEAT_INTERVAL),
|
||||
presence: Mutex::new(PresenceTracker::default()),
|
||||
});
|
||||
|
||||
let ordinary_routes = Router::new()
|
||||
@@ -581,7 +583,28 @@ async fn interact(
|
||||
}
|
||||
|
||||
// req: push/001 req: push/003 req: examples/001
|
||||
async fn sync_broadcast(Query(params): Query<BTreeMap<String, String>>) -> Response {
|
||||
struct PresenceSignal {
|
||||
channel: Channel,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl PresenceScope for PresenceSignal {
|
||||
fn presence_channel(&self) -> Channel {
|
||||
self.channel.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[hemx_sync::presence]
|
||||
fn presence_changed(signal: PresenceSignal) -> impl hemx::IntoEffect {
|
||||
board::presence.put(&Presence {
|
||||
count: u64::try_from(signal.count).expect("presence count fits u64"),
|
||||
})
|
||||
}
|
||||
|
||||
async fn sync_broadcast(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Query(params): Query<BTreeMap<String, String>>,
|
||||
) -> Response {
|
||||
let Some(channel) = params
|
||||
.get("channel")
|
||||
.and_then(|channel| Channel::new(channel).ok())
|
||||
@@ -591,10 +614,21 @@ async fn sync_broadcast(Query(params): Query<BTreeMap<String, String>>) -> Respo
|
||||
if channel.as_str() != "board" {
|
||||
return StatusCode::NOT_FOUND.into_response();
|
||||
}
|
||||
let batch = board::presence
|
||||
.put(&Presence { count: 7 })
|
||||
.into_batch(ui::BUILD_FINGERPRINT);
|
||||
let broadcast = SyncEffect::broadcast(channel, batch);
|
||||
let member = params.get("member").cloned();
|
||||
let count = {
|
||||
let mut presence = state.presence.lock().unwrap();
|
||||
match (params.get("action").map(String::as_str), member.as_ref()) {
|
||||
(Some("join"), Some(member)) => presence.join(channel.clone(), member.clone()).count,
|
||||
(Some("leave"), Some(member)) => presence.leave(&channel, member).count,
|
||||
(None | Some("snapshot"), None) => presence.count(&channel),
|
||||
_ => {
|
||||
return (StatusCode::BAD_REQUEST, "invalid presence action or member")
|
||||
.into_response();
|
||||
}
|
||||
}
|
||||
};
|
||||
let broadcast =
|
||||
presence_changed(PresenceSignal { channel, count }).into_broadcast(ui::BUILD_FINGERPRINT);
|
||||
let (_channel, effect_batch) = broadcast.into_parts();
|
||||
sse(stream::iter([Ok::<_, Infallible>(effect_batch)]).boxed()).into_response()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user