message_cache.rs (5047B)
1 //! Message caching with O(1) deduplication 2 //! 3 //! Stores messages by chat_id with HashSet-based dedup to avoid 4 //! duplicate messages from SSE reconnections. 5 6 use crate::generated::{Chat, Message, UserChat}; 7 use serde::Serialize; 8 use std::collections::{HashMap, HashSet}; 9 10 /// Display-ready chat for QML (with computed/default fields) 11 #[derive(Debug, Clone, Serialize)] 12 pub struct DisplayChat { 13 pub id: i64, 14 pub title: String, 15 pub created_at: String, 16 pub created_by: String, 17 // Display-only fields (defaults for now) 18 pub avatar: String, 19 pub is_online: bool, 20 pub last_message: String, 21 pub unread_count: i32, 22 } 23 24 impl DisplayChat { 25 pub fn from_chat(chat: &Chat) -> Self { 26 Self { 27 id: chat.id, 28 title: chat.title.clone(), 29 created_at: chat.created_at.clone(), 30 created_by: chat.created_by.clone(), 31 avatar: String::new(), 32 is_online: false, 33 last_message: String::new(), 34 unread_count: 0, 35 } 36 } 37 38 pub fn from_user_chat(chat: &UserChat) -> Self { 39 Self { 40 id: chat.id, 41 title: chat.title.clone(), 42 created_at: chat.created_at.clone(), 43 created_by: chat.created_by.clone(), 44 avatar: String::new(), 45 is_online: false, 46 last_message: String::new(), 47 unread_count: 0, 48 } 49 } 50 } 51 52 /// Display-ready message for QML (pre-computed fields) 53 #[derive(Debug, Clone, Serialize)] 54 pub struct DisplayMessage { 55 pub id: i64, 56 pub chat_id: i64, 57 pub sender_id: String, 58 pub content: String, 59 pub sent_at: String, 60 pub is_outgoing: bool, 61 pub is_read: bool, 62 } 63 64 impl DisplayMessage { 65 /// Transform a Message into a DisplayMessage 66 pub fn from_message(msg: &Message, current_user_id: &str) -> Self { 67 let sender_id = msg.sender_id.clone().unwrap_or_default(); 68 Self { 69 id: msg.id, 70 chat_id: msg.chat_id, 71 sender_id: sender_id.clone(), 72 content: msg.content.clone(), 73 sent_at: msg.sent_at.clone(), 74 is_outgoing: sender_id == current_user_id, 75 is_read: true, 76 } 77 } 78 } 79 80 /// Message cache with O(1) deduplication 81 pub struct MessageCache { 82 /// Messages by chat_id 83 messages: HashMap<i64, Vec<DisplayMessage>>, 84 /// Seen message IDs for deduplication 85 seen_ids: HashSet<i64>, 86 } 87 88 impl Default for MessageCache { 89 fn default() -> Self { 90 Self::new() 91 } 92 } 93 94 impl MessageCache { 95 pub fn new() -> Self { 96 Self { 97 messages: HashMap::new(), 98 seen_ids: HashSet::new(), 99 } 100 } 101 102 /// Add a message if not already seen. Returns Some(DisplayMessage) if new, None if duplicate. 103 pub fn add_message(&mut self, msg: Message, current_user_id: &str) -> Option<DisplayMessage> { 104 if self.seen_ids.contains(&msg.id) { 105 return None; 106 } 107 108 self.seen_ids.insert(msg.id); 109 let display_msg = DisplayMessage::from_message(&msg, current_user_id); 110 111 self.messages 112 .entry(msg.chat_id) 113 .or_default() 114 .push(display_msg.clone()); 115 116 Some(display_msg) 117 } 118 119 /// Get all messages for a chat 120 pub fn get_messages(&self, chat_id: i64) -> &[DisplayMessage] { 121 self.messages.get(&chat_id).map(|v| v.as_slice()).unwrap_or(&[]) 122 } 123 124 /// Set messages for a chat (initial load), replacing any existing 125 pub fn set_messages(&mut self, chat_id: i64, messages: Vec<Message>, current_user_id: &str) -> Vec<DisplayMessage> { 126 let display_messages: Vec<DisplayMessage> = messages 127 .iter() 128 .map(|msg| { 129 self.seen_ids.insert(msg.id); 130 DisplayMessage::from_message(msg, current_user_id) 131 }) 132 .collect(); 133 134 self.messages.insert(chat_id, display_messages.clone()); 135 display_messages 136 } 137 138 /// Prepend older messages (for pagination) 139 pub fn prepend_messages(&mut self, chat_id: i64, messages: Vec<Message>, current_user_id: &str) -> Vec<DisplayMessage> { 140 let display_messages: Vec<DisplayMessage> = messages 141 .iter() 142 .filter_map(|msg| { 143 if self.seen_ids.contains(&msg.id) { 144 None 145 } else { 146 self.seen_ids.insert(msg.id); 147 Some(DisplayMessage::from_message(msg, current_user_id)) 148 } 149 }) 150 .collect(); 151 152 let chat_messages = self.messages.entry(chat_id).or_default(); 153 let mut new_messages = display_messages.clone(); 154 new_messages.append(chat_messages); 155 *chat_messages = new_messages; 156 157 display_messages 158 } 159 160 /// Clear messages for a chat 161 pub fn clear_chat(&mut self, chat_id: i64) { 162 if let Some(messages) = self.messages.remove(&chat_id) { 163 for msg in messages { 164 self.seen_ids.remove(&msg.id); 165 } 166 } 167 } 168 }