cnats: NATS-native chat with Leptos SSR and Kanidm SSO
Initial import plus deployment packaging: multi-stage Dockerfile (cargo-leptos build -> debian-slim runtime), .dockerignore, and a dev-only docker-compose (app + local NATS). Production deployment lives in the infrastructure repo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
//! Server-Sent Events bridge: one NATS subscription per connected browser,
|
||||
//! scoped to a single room subject.
|
||||
|
||||
use std::{convert::Infallible, time::Duration};
|
||||
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::sse::{Event, KeepAlive, Sse},
|
||||
};
|
||||
use futures::{Stream, StreamExt};
|
||||
use tower_sessions::Session;
|
||||
|
||||
use crate::auth::{User, SESSION_USER_KEY};
|
||||
use crate::chat::{is_valid_room, room_subject};
|
||||
|
||||
use super::AppState;
|
||||
|
||||
/// GET /sse/{room} — stream the room's NATS subject to the browser.
|
||||
pub async fn room_events(
|
||||
Path(room): Path<String>,
|
||||
State(state): State<AppState>,
|
||||
session: Session,
|
||||
) -> Result<Sse<impl Stream<Item = Result<Event, Infallible>>>, (StatusCode, &'static str)> {
|
||||
let signed_in = session
|
||||
.get::<User>(SESSION_USER_KEY)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.is_some();
|
||||
if !signed_in {
|
||||
return Err((StatusCode::UNAUTHORIZED, "sign in first"));
|
||||
}
|
||||
if !is_valid_room(&room) {
|
||||
return Err((StatusCode::NOT_FOUND, "unknown room"));
|
||||
}
|
||||
|
||||
let subscriber = state
|
||||
.nats
|
||||
.subscribe(room_subject(&room))
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("nats subscribe failed: {e}");
|
||||
(StatusCode::BAD_GATEWAY, "message bus unavailable")
|
||||
})?;
|
||||
|
||||
let stream = subscriber.map(|msg| {
|
||||
Ok(Event::default()
|
||||
.event("message")
|
||||
.data(String::from_utf8_lossy(&msg.payload).into_owned()))
|
||||
});
|
||||
|
||||
Ok(Sse::new(stream).keep_alive(
|
||||
KeepAlive::new()
|
||||
.interval(Duration::from_secs(15))
|
||||
.text("ping"),
|
||||
))
|
||||
}
|
||||
Reference in New Issue
Block a user