Skip to main content

missiond_core/core/
inbox.rs

1//! Inbox - Message inbox management
2//!
3//! Manages messages from completed tasks.
4
5use crate::db::MissionDB;
6use crate::types::InboxMessage;
7use std::sync::Arc;
8use tracing::{debug, info};
9use uuid::Uuid;
10
11/// Inbox Manager
12///
13/// Manages the message inbox for task results.
14pub struct Inbox {
15    db: Arc<MissionDB>,
16}
17
18impl Inbox {
19    /// Create a new Inbox
20    pub fn new(db: Arc<MissionDB>) -> Self {
21        Self { db }
22    }
23
24    /// Get messages
25    ///
26    /// # Arguments
27    /// * `unread_only` - If true, only return unread messages
28    /// * `limit` - Maximum number of messages to return
29    pub fn get_messages(&self, unread_only: bool, limit: usize) -> Vec<InboxMessage> {
30        self.db
31            .get_inbox_messages(unread_only, limit as i64)
32            .unwrap_or_default()
33    }
34
35    /// Mark a message as read
36    pub fn mark_read(&self, message_id: &str) {
37        let _ = self.db.mark_inbox_read(message_id);
38        debug!(message_id = %message_id, "Message marked as read");
39    }
40
41    /// Mark all messages as read
42    pub fn mark_all_read(&self) {
43        let messages = self
44            .db
45            .get_inbox_messages(true, 10000)
46            .unwrap_or_default();
47        let count = messages.len();
48
49        for msg in messages {
50            let _ = self.db.mark_inbox_read(&msg.id);
51        }
52
53        info!(count = count, "All messages marked as read");
54    }
55
56    /// Get unread message count
57    pub fn get_unread_count(&self) -> usize {
58        self.db
59            .get_inbox_messages(true, 10000)
60            .map(|v| v.len())
61            .unwrap_or(0)
62    }
63
64    /// Add a message to the inbox
65    pub fn add_message(&self, task_id: &str, from_role: &str, content: &str) {
66        let msg = InboxMessage {
67            id: Uuid::new_v4().to_string(),
68            task_id: task_id.to_string(),
69            from_role: from_role.to_string(),
70            content: content.to_string(),
71            read: false,
72            created_at: chrono::Utc::now().timestamp_millis(),
73        };
74
75        let _ = self.db.insert_inbox_message(&msg);
76        debug!(message_id = %msg.id, task_id = %task_id, "Message added to inbox");
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use tempfile::tempdir;
84
85    fn create_test_db() -> Arc<MissionDB> {
86        let dir = tempdir().unwrap();
87        let db_path = dir.path().join("test.db");
88        Arc::new(MissionDB::open(db_path).unwrap())
89    }
90
91    #[test]
92    fn test_add_and_get_messages() {
93        let db = create_test_db();
94        let inbox = Inbox::new(db);
95
96        // Add messages
97        inbox.add_message("task-1", "worker", "Result 1");
98        inbox.add_message("task-2", "specialist", "Result 2");
99
100        // Get all messages
101        let messages = inbox.get_messages(false, 10);
102        assert_eq!(messages.len(), 2);
103
104        // Get unread messages
105        let unread = inbox.get_messages(true, 10);
106        assert_eq!(unread.len(), 2);
107
108        // Check content
109        assert!(messages.iter().any(|m| m.content == "Result 1"));
110        assert!(messages.iter().any(|m| m.content == "Result 2"));
111    }
112
113    #[test]
114    fn test_mark_read() {
115        let db = create_test_db();
116        let inbox = Inbox::new(db);
117
118        // Add a message
119        inbox.add_message("task-1", "worker", "Result");
120
121        // Get the message ID
122        let messages = inbox.get_messages(true, 10);
123        assert_eq!(messages.len(), 1);
124        let msg_id = &messages[0].id;
125
126        // Mark as read
127        inbox.mark_read(msg_id);
128
129        // Should no longer appear in unread
130        let unread = inbox.get_messages(true, 10);
131        assert_eq!(unread.len(), 0);
132
133        // But still appears in all messages
134        let all = inbox.get_messages(false, 10);
135        assert_eq!(all.len(), 1);
136    }
137
138    #[test]
139    fn test_mark_all_read() {
140        let db = create_test_db();
141        let inbox = Inbox::new(db);
142
143        // Add multiple messages
144        inbox.add_message("task-1", "worker", "Result 1");
145        inbox.add_message("task-2", "worker", "Result 2");
146        inbox.add_message("task-3", "worker", "Result 3");
147
148        // All should be unread
149        assert_eq!(inbox.get_unread_count(), 3);
150
151        // Mark all read
152        inbox.mark_all_read();
153
154        // None should be unread
155        assert_eq!(inbox.get_unread_count(), 0);
156    }
157
158    #[test]
159    fn test_get_unread_count() {
160        let db = create_test_db();
161        let inbox = Inbox::new(db);
162
163        assert_eq!(inbox.get_unread_count(), 0);
164
165        inbox.add_message("task-1", "worker", "Result 1");
166        assert_eq!(inbox.get_unread_count(), 1);
167
168        inbox.add_message("task-2", "worker", "Result 2");
169        assert_eq!(inbox.get_unread_count(), 2);
170
171        // Mark one as read
172        let messages = inbox.get_messages(true, 1);
173        inbox.mark_read(&messages[0].id);
174
175        assert_eq!(inbox.get_unread_count(), 1);
176    }
177
178    #[test]
179    fn test_limit() {
180        let db = create_test_db();
181        let inbox = Inbox::new(db);
182
183        // Add 5 messages
184        for i in 0..5 {
185            inbox.add_message(&format!("task-{}", i), "worker", &format!("Result {}", i));
186        }
187
188        // Request only 3
189        let messages = inbox.get_messages(true, 3);
190        assert_eq!(messages.len(), 3);
191    }
192}