1use crate::crdt::{
23 CRDTMessage, EntityType, MessageContent, MissingRange, SyncResponse, sort_messages_causally,
24};
25use crate::message_sync::MessageSyncService;
26use serde::{Deserialize, Serialize};
27use std::sync::Arc;
28
29#[derive(Debug, thiserror::Error)]
31pub enum MessageServiceError {
32 #[error("Message sync error: {0}")]
33 SyncError(String),
34
35 #[error("Entity not found: {0}")]
36 EntityNotFound(String),
37
38 #[error("Invalid entity type: {0}")]
39 InvalidEntityType(String),
40
41 #[error("Serialization error: {0}")]
42 Serialization(#[from] serde_json::Error),
43
44 #[error("IO error: {0}")]
45 Io(#[from] std::io::Error),
46}
47
48pub type MessageServiceResult<T> = Result<T, MessageServiceError>;
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct ReceiveResult {
54 pub accepted: bool,
55 pub out_of_order: bool,
56 pub missing_ranges: Vec<MissingRange>,
57}
58
59pub struct MessageService {
61 message_sync: Arc<MessageSyncService>,
62}
63
64impl MessageService {
65 pub fn new(peer_id: String) -> Self {
67 let message_sync = Arc::new(MessageSyncService::new(peer_id));
68 Self { message_sync }
69 }
70
71 pub async fn send_message(
73 &self,
74 entity_id: String,
75 entity_type: EntityType,
76 content: MessageContent,
77 reply_to_id: Option<String>,
78 ) -> MessageServiceResult<CRDTMessage> {
79 let has_text = !content.text.trim().is_empty();
82 let has_attachments = content.attachments.as_ref().is_some_and(|a| !a.is_empty());
83
84 if !has_text && !has_attachments {
85 return Err(MessageServiceError::SyncError(
86 "Message must have text or attachments".to_string(),
87 ));
88 }
89
90 self.message_sync
91 .send_message(entity_id, entity_type, content, reply_to_id)
92 .await
93 .map_err(|e| MessageServiceError::SyncError(e.to_string()))
94 }
95
96 pub async fn receive_message(
98 &self,
99 message: CRDTMessage,
100 ) -> MessageServiceResult<ReceiveResult> {
101 let result = self
102 .message_sync
103 .receive_message(message)
104 .await
105 .map_err(|e| MessageServiceError::SyncError(e.to_string()))?;
106
107 Ok(ReceiveResult {
108 accepted: result.accepted,
109 out_of_order: result.out_of_order,
110 missing_ranges: result.missing_ranges.unwrap_or_default(),
111 })
112 }
113
114 pub async fn get_entity_messages(
116 &self,
117 entity_id: String,
118 ) -> MessageServiceResult<SyncResponse> {
119 self.message_sync
120 .get_all_messages(&entity_id)
121 .await
122 .map_err(|e| MessageServiceError::SyncError(e.to_string()))
123 }
124
125 pub async fn get_thread_messages(
127 &self,
128 entity_id: String,
129 parent_message_id: String,
130 ) -> MessageServiceResult<Vec<CRDTMessage>> {
131 let sync_response = self
132 .message_sync
133 .get_all_messages(&entity_id)
134 .await
135 .map_err(|e| MessageServiceError::SyncError(e.to_string()))?;
136
137 let thread_messages: Vec<CRDTMessage> = sync_response
139 .messages
140 .into_iter()
141 .filter(|msg| {
142 msg.metadata
143 .reply_to_id
144 .as_ref()
145 .map(|id| id == &parent_message_id)
146 .unwrap_or(false)
147 })
148 .collect();
149
150 Ok(thread_messages)
151 }
152
153 pub async fn get_entity_sync_state(
155 &self,
156 entity_id: String,
157 entity_type: EntityType,
158 ) -> MessageServiceResult<crate::crdt::EntitySyncState> {
159 let sync_response = self.get_entity_messages(entity_id.clone()).await?;
160
161 Ok(crate::crdt::EntitySyncState {
162 entity_id,
163 entity_type,
164 vector_clock: sync_response.vector_clock,
165 last_sync_time: std::time::SystemTime::now()
166 .duration_since(std::time::UNIX_EPOCH)
167 .unwrap_or_default()
168 .as_secs(),
169 message_count: sync_response.messages.len(),
170 missing_messages: vec![], out_of_order_messages: vec![], })
173 }
174
175 pub async fn send_direct_messages(
177 &self,
178 recipients: Vec<String>,
179 content: MessageContent,
180 ) -> MessageServiceResult<Vec<String>> {
181 let mut message_ids = Vec::new();
182
183 for recipient in recipients {
184 let entity_id = format!("dm:{}", recipient);
186
187 let message = self
188 .message_sync
189 .send_message(entity_id, EntityType::Person, content.clone(), None)
190 .await
191 .map_err(|e| MessageServiceError::SyncError(e.to_string()))?;
192
193 message_ids.push(message.metadata.id);
194 }
195
196 Ok(message_ids)
197 }
198
199 pub async fn get_direct_messages(
201 &self,
202 other_peer_id: String,
203 ) -> MessageServiceResult<SyncResponse> {
204 let entity_id = format!("dm:{}", other_peer_id);
205 self.message_sync
206 .get_all_messages(&entity_id)
207 .await
208 .map_err(|e| MessageServiceError::SyncError(e.to_string()))
209 }
210
211 pub async fn send_to_channel(
217 &self,
218 channel_id: String,
219 content: MessageContent,
220 ) -> MessageServiceResult<String> {
221 let message = self
222 .send_message(channel_id, EntityType::Channel, content, None)
223 .await?;
224 Ok(message.metadata.id)
225 }
226
227 pub async fn send_thread_reply(
229 &self,
230 entity_id: String,
231 entity_type: EntityType,
232 thread_id: String,
233 content: MessageContent,
234 ) -> MessageServiceResult<String> {
235 let message = self
236 .send_message(entity_id, entity_type, content, Some(thread_id))
237 .await?;
238 Ok(message.metadata.id)
239 }
240
241 pub async fn get_channel_messages(
243 &self,
244 channel_id: String,
245 ) -> MessageServiceResult<Vec<CRDTMessage>> {
246 let sync_response = self.get_entity_messages(channel_id).await?;
247 Ok(sync_response.messages)
248 }
249
250 pub fn sort_messages(&self, messages: &mut [CRDTMessage]) {
252 sort_messages_causally(messages);
253 }
254
255 pub async fn delete_message(
256 &self,
257 entity_id: &str,
258 message_id: &str,
259 ) -> MessageServiceResult<bool> {
260 self.message_sync
261 .delete_message(entity_id, message_id)
262 .await
263 .map_err(|e| MessageServiceError::SyncError(e.to_string()))
264 }
265
266 pub async fn edit_message(
267 &self,
268 entity_id: &str,
269 message_id: &str,
270 new_text: String,
271 ) -> MessageServiceResult<u64> {
272 self.message_sync
273 .edit_message(entity_id, message_id, new_text)
274 .await
275 .map_err(|e| MessageServiceError::SyncError(e.to_string()))
276 }
277
278 pub async fn add_reaction(
279 &self,
280 entity_id: &str,
281 message_id: &str,
282 emoji: String,
283 peer_id: String,
284 ) -> MessageServiceResult<()> {
285 self.message_sync
286 .add_reaction(entity_id, message_id, emoji, peer_id)
287 .await
288 .map_err(|e| MessageServiceError::SyncError(e.to_string()))
289 }
290
291 pub async fn remove_reaction(
292 &self,
293 entity_id: &str,
294 message_id: &str,
295 emoji: String,
296 peer_id: String,
297 ) -> MessageServiceResult<()> {
298 self.message_sync
299 .remove_reaction(entity_id, message_id, emoji, peer_id)
300 .await
301 .map_err(|e| MessageServiceError::SyncError(e.to_string()))
302 }
303
304 pub async fn get_reactions(
305 &self,
306 entity_id: &str,
307 message_id: &str,
308 ) -> MessageServiceResult<Vec<crate::crdt::Reaction>> {
309 self.message_sync
310 .get_reactions(entity_id, message_id)
311 .await
312 .map_err(|e| MessageServiceError::SyncError(e.to_string()))
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 async fn create_test_service() -> MessageService {
321 MessageService::new("test-peer-123".to_string())
322 }
323
324 #[tokio::test]
325 async fn test_message_service_creation() {
326 let _service = create_test_service().await;
327 }
329
330 #[tokio::test]
331 async fn test_send_message() {
332 let service = create_test_service().await;
333
334 let content = MessageContent {
335 text: "Hello, world!".to_string(),
336 author: "test-user".to_string(),
337 attachments: None,
338 };
339
340 let result = service
341 .send_message(
342 "test-channel".to_string(),
343 EntityType::Channel,
344 content,
345 None,
346 )
347 .await;
348
349 match result {
351 Ok(message) => {
352 assert_eq!(message.content.text, "Hello, world!");
353 assert_eq!(message.content.author, "test-user");
354 println!("✅ Send message test passed!");
355 }
356 Err(e) => {
357 println!(
359 "⚠️ Send message returned error (expected in test env): {}",
360 e
361 );
362 }
363 }
364 }
365
366 #[tokio::test]
367 async fn test_send_thread_reply() {
368 let service = create_test_service().await;
369
370 let content = MessageContent {
371 text: "This is a reply".to_string(),
372 author: "test-user".to_string(),
373 attachments: None,
374 };
375
376 let result = service
377 .send_thread_reply(
378 "test-channel".to_string(),
379 EntityType::Channel,
380 "parent-msg-123".to_string(),
381 content,
382 )
383 .await;
384
385 match result {
386 Ok(message_id) => {
387 assert!(!message_id.is_empty());
388 println!("✅ Thread reply test passed!");
389 }
390 Err(e) => {
391 println!(
392 "⚠️ Thread reply returned error (expected in test env): {}",
393 e
394 );
395 }
396 }
397 }
398
399 #[tokio::test]
400 async fn test_get_entity_sync_state() {
401 let service = create_test_service().await;
402
403 let result = service
404 .get_entity_sync_state("test-entity".to_string(), EntityType::Channel)
405 .await;
406
407 match result {
408 Ok(sync_state) => {
409 assert_eq!(sync_state.entity_id, "test-entity");
410 assert_eq!(sync_state.entity_type, EntityType::Channel);
411 println!("✅ Get entity sync state test passed!");
412 }
413 Err(e) => {
414 println!(
415 "⚠️ Get sync state returned error (expected in test env): {}",
416 e
417 );
418 }
419 }
420 }
421}