//! SSE pour suivre les évènements de playlists (updates + morceaux joués). //! //! Route type : `GET /api/playlists/events?playlist_id=foo` use crate::{subscribe_events, PlaylistEventKind}; #[cfg(feature = "pmoserver")] use async_stream::stream; use axum::{ extract::Query, response::sse::{Event, KeepAlive, Sse}, response::IntoResponse, Router, }; use serde::{Deserialize, Serialize}; #[cfg(feature = "pmoserver")] #[derive(Debug, Default, Deserialize)] #[cfg_attr(feature = "pmoserver", derive(utoipa::IntoParams, utoipa::ToSchema))] pub struct EventsQuery { /// Filtrer sur une playlist précise (optionnel). #[serde(default)] pub playlist_id: Option, } #[derive(Debug, Serialize)] #[cfg_attr(feature = "pmoserver", derive(utoipa::ToSchema))] pub struct EventPayload { pub playlist_id: String, pub kind: String, pub cache_pk: Option, pub qualifier: Option, pub timestamp: chrono::DateTime, pub source_client: Option, } /// Handler SSE : diffuse les évènements playlist enrichis. #[utoipa::path( get, path = "/api/playlists/events", tag = "playlists", params(EventsQuery), responses( (status = 200, description = "Flux SSE des évènements playlists (updated, track_played)", content_type = "text/event-stream") ) )] pub async fn playlist_events_sse(Query(params): Query) -> impl IntoResponse { let mut rx = subscribe_events(); let stream = stream! { while let Ok(envelope) = rx.recv().await { if let Some(filter) = ¶ms.playlist_id { if &envelope.event.playlist_id != filter { continue; } } let (kind, cache_pk, qualifier) = match &envelope.event.kind { PlaylistEventKind::Updated => ("updated", None, None), PlaylistEventKind::PkUpdated { old_pk: _, new_pk: _ } => { // PK swap silencieux - envoyer quand même l'événement SSE pour debug/monitoring ("pk_updated", None, None) } PlaylistEventKind::TrackPlayed { cache_pk, qualifier } => { ("track_played", Some(cache_pk.as_str()), Some(qualifier.as_str())) } }; let ts = chrono::DateTime::::from(envelope.timestamp); let payload = EventPayload { playlist_id: envelope.event.playlist_id.clone(), kind: kind.to_string(), cache_pk: cache_pk.map(|s| s.to_string()), qualifier: qualifier.map(|s| s.to_string()), timestamp: ts, source_client: envelope.source_client.clone(), }; if let Ok(json) = serde_json::to_string(&payload) { yield Ok::<_, axum::Error>(Event::default().event("playlist").data(json)); } } }; Sse::new(stream).keep_alive(KeepAlive::default()) } /// Router prêt à être monté (ex: `/api/playlists/events`). pub fn playlist_events_router() -> Router { use axum::routing::get; Router::new().route("/events", get(playlist_events_sse)) }