use serde::{Deserialize, Serialize}; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum RoomEvent { MessagePublished { room_id: Uuid, seq: i64, message: serde_json::Value, }, MessageEdited { room_id: Uuid, seq: i64, message: serde_json::Value, }, MessageDeleted { room_id: Uuid, seq: i64, user_id: i64, message_id: Uuid, }, MessagesDeleted { room_id: Uuid, seq: i64, user_id: i64, message_ids: Vec, }, MessagePinned { room_id: Uuid, seq: i64, user_id: i64, message_id: Uuid, pinned: bool, }, ReactionUpdated { room_id: Uuid, user_id: i64, message_id: Uuid, reactions: serde_json::Value, }, Typing { room_id: Uuid, user_id: i64, thread_id: Option, ttl_secs: u64, }, RoomUpdated { room_id: Uuid, }, MemberJoined { room_id: Uuid, user_id: i64, }, MemberLeft { room_id: Uuid, user_id: i64, }, ThreadUpdated { room_id: Uuid, thread_id: Uuid, }, /// 语音频道在场集合变化(webhook 加入/离开、对账回写)。 VoicePresence { room_id: Uuid, }, /// 举手集合变化(举手、审批、收回、离开)。 VoiceHands { room_id: Uuid, }, /// 参与者窗口布局变化(拖动/缩放,房间内所有人共享)。 VoiceLayout { room_id: Uuid, }, /// 主持人动作(静音、解除静音、移出、授权)。 VoiceModeration { room_id: Uuid, user_id: i64, action: String, }, } impl RoomEvent { pub fn room_id(&self) -> Uuid { match self { Self::MessagePublished { room_id, .. } | Self::MessageEdited { room_id, .. } | Self::MessageDeleted { room_id, .. } | Self::MessagesDeleted { room_id, .. } | Self::MessagePinned { room_id, .. } | Self::ReactionUpdated { room_id, .. } | Self::Typing { room_id, .. } | Self::RoomUpdated { room_id, .. } | Self::MemberJoined { room_id, .. } | Self::MemberLeft { room_id, .. } | Self::ThreadUpdated { room_id, .. } | Self::VoicePresence { room_id } | Self::VoiceHands { room_id } | Self::VoiceLayout { room_id } | Self::VoiceModeration { room_id, .. } => *room_id, } } pub fn seq(&self) -> Option { match self { Self::MessagePublished { seq, .. } | Self::MessageEdited { seq, .. } | Self::MessageDeleted { seq, .. } | Self::MessagesDeleted { seq, .. } | Self::MessagePinned { seq, .. } => Some(*seq), _ => None, } } } /// 房间/用户事件的唯一出口:写 `event_outbox`,由 outbox relay 投递给 KenEvent。 /// /// 这里不再维护进程内的订阅表,也不再向 Redis 发 `PUBLISH`——实时通道归 /// KenEvent(MQ → socket),本服务只负责把"发生了什么"持久化下来。 #[derive(Clone)] pub struct Realtime { database: std::sync::Arc, } impl Realtime { pub fn new(database: std::sync::Arc) -> Self { Self { database } } pub async fn publish_room(&self, event: &RoomEvent) { if let Err(error) = crate::share::events::enqueue_room(&self.database, event).await { tracing::warn!(%error, room_id = %event.room_id(), "chat room event enqueue failed"); } } pub async fn publish_user(&self, uid: i64, event: &T) { if let Ok(value) = serde_json::to_value(event) && let Err(error) = crate::share::events::enqueue_user(&self.database, uid, &value).await { tracing::warn!(%error, %uid, "chat user event enqueue failed"); } } }