use sea_orm::DatabaseConnection; use serde_json::Value; use uuid::Uuid; use crate::errors::ChatError; use crate::share::realtime::RoomEvent; pub fn room_kind(event: &RoomEvent) -> &'static str { match event { RoomEvent::MessagePublished { .. } => "chat.message.create", RoomEvent::MessageEdited { .. } => "chat.message.update", RoomEvent::MessageDeleted { .. } => "chat.message.delete", RoomEvent::MessagesDeleted { .. } => "chat.messages.delete", RoomEvent::MessagePinned { pinned: true, .. } => "chat.pin.create", RoomEvent::MessagePinned { pinned: false, .. } => "chat.pin.delete", RoomEvent::ReactionUpdated { .. } => "chat.reaction.update", RoomEvent::Typing { .. } => "chat.typing.create", RoomEvent::RoomUpdated { .. } => "chat.room.update", RoomEvent::MemberJoined { .. } => "chat.member.create", RoomEvent::MemberLeft { .. } => "chat.member.delete", RoomEvent::ThreadUpdated { .. } => "chat.thread.update", RoomEvent::VoicePresence { .. } => "chat.voice.presence", RoomEvent::VoiceHands { .. } => "chat.voice.hand", RoomEvent::VoiceLayout { .. } => "chat.voice.layout", RoomEvent::VoiceModeration { .. } => "chat.voice.moderate", } } pub fn user_kind(event: &Value) -> Option<&'static str> { match event.get("kind").and_then(Value::as_str) { Some("mention") => Some("chat.mention.create"), Some("reminder") => Some("chat.reminder.create"), Some("room_added") => Some("chat.membership.create"), Some("read_marker") => Some("chat.read_state.update"), Some("presence") => Some("chat.presence.update"), _ => None, } } /// 房间事件指向的具体消息;`None` 表示事件只描述房间级的状态变化。 fn room_message_id(event: &RoomEvent) -> Option { let from_value = |message: &Value| -> Option { message .get("id") .and_then(Value::as_str) .and_then(|id| Uuid::parse_str(id).ok()) }; match event { RoomEvent::MessagePublished { message, .. } | RoomEvent::MessageEdited { message, .. } => { from_value(message) } RoomEvent::MessageDeleted { message_id, .. } | RoomEvent::MessagePinned { message_id, .. } | RoomEvent::ReactionUpdated { message_id, .. } => Some(*message_id), _ => None, } } fn room_thread_id(event: &RoomEvent) -> Option { match event { RoomEvent::ThreadUpdated { thread_id, .. } => Some(*thread_id), _ => None, } } fn room_actor(event: &RoomEvent) -> Option { match event { RoomEvent::Typing { user_id, .. } | RoomEvent::MemberJoined { user_id, .. } | RoomEvent::MemberLeft { user_id, .. } | RoomEvent::VoiceModeration { user_id, .. } => Some(*user_id), RoomEvent::MessagePublished { message, .. } | RoomEvent::MessageEdited { message, .. } => { message.get("author_id").and_then(Value::as_i64) } _ => None, } } pub async fn enqueue_room( db: &DatabaseConnection, enabled: bool, event: &RoomEvent, ) -> Result<(), ChatError> { if !enabled { return Ok(()); } let mut labels = queue::Labels::new().with("room_id", event.room_id()); if let Some(actor) = room_actor(event) { labels.insert("user_id", actor); } if let Some(message_id) = room_message_id(event) { labels.insert("message_id", message_id); } if let Some(thread_id) = room_thread_id(event) { labels.insert("thread_id", thread_id); } if let Some(request_id) = export::events::request_id() { labels.insert("request_id", request_id); } export::events::enqueue( db, enabled, room_kind(event), labels, event, format!("chat:room:{}", event.room_id()), ) .await?; Ok(()) } pub async fn enqueue_user( db: &DatabaseConnection, enabled: bool, uid: i64, event: &Value, ) -> Result<(), ChatError> { if !enabled { return Ok(()); } let Some(kind) = user_kind(event) else { let unknown = event .get("kind") .and_then(Value::as_str) .unwrap_or_default(); tracing::warn!(kind = %unknown, %uid, "unknown chat user event kind; skipping the outbox write"); return Ok(()); }; let mut labels = queue::Labels::new().with("user_id", uid); if let Some(room_id) = event.get("room_id").and_then(Value::as_str) { labels.insert("room_id", room_id); } if let Some(request_id) = export::events::request_id() { labels.insert("request_id", request_id); } export::events::enqueue(db, enabled, kind, labels, event, format!("chat:user:{uid}")).await?; Ok(()) }