use uuid::Uuid; use crate::event::{RoomInfo, UserInfo}; use crate::{ ChannelBus, ChannelError, ChannelResult, search::{SearchEngine, SearchQuery}, }; use super::WsHandler; use super::WsOutEvent; impl WsHandler { pub(super) async fn search( bus: &ChannelBus, user_id: Uuid, q: String, room: Option, limit: Option, offset: Option, ) -> ChannelResult> { if let Some(room_id) = room { Self::ensure_room_access(bus, user_id, room_id).await?; } else { return Err(ChannelError::Validation( "room is required for websocket search".to_string(), )); } let engine = SearchEngine::new(bus.inner.db.clone()); let result = engine .search(SearchQuery { query: q.clone(), room_id: room, user_id: None, limit: limit.unwrap_or(50), offset: offset.unwrap_or(0), }) .await?; let author_ids: Vec = result.hits.iter().map(|h| h.sender_id).collect(); let message_ids: Vec = result.hits.iter().map(|h| h.message_id).collect(); let user_map = bus.lookup_users(&author_ids).await.unwrap_or_default(); let reactions = Self::reaction_groups_for_messages(bus, user_id, &message_ids) .await .unwrap_or_default(); let search_room = match room { Some(r) => Some( bus.lookup_room(r) .await .unwrap_or_else(|_| RoomInfo::unknown(r)), ), None => None, }; let search_msg_room = search_room .clone() .unwrap_or_else(|| RoomInfo::unknown(room.unwrap_or_default())); let data = crate::event::search::SearchResultService { q, room: search_room, messages: result .hits .into_iter() .map(|h| { let sender = user_map .get(&h.sender_id) .cloned() .unwrap_or_else(|| UserInfo::unknown(h.sender_id)); crate::event::search::SearchMessageHitService { message: crate::event::message::MessageNewService { id: h.message_id, seq: 0, room: search_msg_room.clone(), sender_type: "user".to_string(), sender, thread: None, in_reply_to: None, content: h.content.clone(), content_type: "text".to_string(), pinned: false, system_type: None, metadata: serde_json::Value::Null, thinking_content: None, thinking_is_chunked: None, send_at: h.send_at, reactions: reactions .get(&h.message_id) .cloned() .unwrap_or_default(), }, highlighted_content: h.highlighted, } }) .collect(), total: result.total as i64, took_ms: 0, }; Ok(Some(WsOutEvent::SearchResult { data })) } }