Skip to main content

agent_office/services/mail/
domain.rs

1use crate::domain::{string_to_node_id, Node, NodeId, Properties, PropertyValue, Timestamp};
2use chrono::Utc;
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6// Domain types for the mail system
7pub type MailboxId = NodeId;
8pub type MailId = NodeId;
9pub type AgentId = String;
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12pub struct Mailbox {
13    pub id: MailboxId,
14    pub owner_id: AgentId,
15    pub name: String,
16    pub created_at: Timestamp,
17}
18
19impl Mailbox {}
20
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
22pub struct Mail {
23    pub id: MailId,
24    pub from_mailbox_id: MailboxId,
25    pub to_mailbox_id: MailboxId,
26    pub subject: String,
27    pub body: String,
28    pub read: bool,
29    pub created_at: Timestamp,
30}
31
32impl Mail {
33    pub fn new(
34        from_mailbox_id: MailboxId,
35        to_mailbox_id: MailboxId,
36        subject: impl Into<String>,
37        body: impl Into<String>,
38    ) -> Self {
39        Self {
40            id: MailId::new_v4(),
41            from_mailbox_id,
42            to_mailbox_id,
43            subject: subject.into(),
44            body: body.into(),
45            read: false,
46            created_at: Utc::now(),
47        }
48    }
49
50    pub fn to_node(&self) -> Node {
51        let mut props = Properties::new();
52        props.insert(
53            "from_mailbox_id".to_string(),
54            PropertyValue::String(self.from_mailbox_id.to_string()),
55        );
56        props.insert(
57            "to_mailbox_id".to_string(),
58            PropertyValue::String(self.to_mailbox_id.to_string()),
59        );
60        props.insert(
61            "subject".to_string(),
62            PropertyValue::String(self.subject.clone()),
63        );
64        props.insert("body".to_string(), PropertyValue::String(self.body.clone()));
65        props.insert("read".to_string(), PropertyValue::Boolean(self.read));
66
67        let mut node = Node::new("mail", props);
68        node.id = self.id;
69        node
70    }
71
72    pub fn from_node(node: &Node) -> Option<Self> {
73        if node.node_type != "mail" {
74            return None;
75        }
76
77        let from_mailbox_id = node.get_property("from_mailbox_id").and_then(|v| match v {
78            PropertyValue::String(s) => Uuid::parse_str(s).ok(),
79            _ => None,
80        })?;
81
82        let to_mailbox_id = node.get_property("to_mailbox_id").and_then(|v| match v {
83            PropertyValue::String(s) => Uuid::parse_str(s).ok(),
84            _ => None,
85        })?;
86
87        let subject = node.get_property("subject").and_then(|v| match v {
88            PropertyValue::String(s) => Some(s.clone()),
89            _ => None,
90        })?;
91
92        let body = node.get_property("body").and_then(|v| match v {
93            PropertyValue::String(s) => Some(s.clone()),
94            _ => None,
95        })?;
96
97        let read = node
98            .get_property("read")
99            .and_then(|v| match v {
100                PropertyValue::Boolean(b) => Some(*b),
101                _ => None,
102            })
103            .unwrap_or(false);
104
105        Some(Self {
106            id: node.id,
107            from_mailbox_id,
108            to_mailbox_id,
109            subject,
110            body,
111            read,
112            created_at: node.created_at,
113        })
114    }
115
116    pub fn mark_as_read(&mut self) {
117        self.read = true;
118    }
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct Agent {
123    pub id: AgentId,
124    pub name: String,
125    pub status: String,
126    pub created_at: Timestamp,
127}
128
129impl Default for Agent {
130    fn default() -> Self {
131        let name = String::from("Unnamed");
132        Self {
133            id: name.clone(),
134            name,
135            status: String::from("offline"),
136            created_at: Utc::now(),
137        }
138    }
139}
140
141impl Agent {
142    pub fn new(name: impl Into<String>) -> Self {
143        let name = name.into();
144        // Use the name as the ID for simplicity (allows IDs like "intern_0")
145        // Or generate a random string if name is not suitable as ID
146        let id = if name.contains(char::is_whitespace) || name.is_empty() {
147            format!(
148                "agent_{}",
149                uuid::Uuid::new_v4().to_string().split('-').next().unwrap()
150            )
151        } else {
152            name.clone()
153        };
154
155        Self {
156            id,
157            name,
158            status: String::from("offline"),
159            created_at: Utc::now(),
160        }
161    }
162
163    pub fn to_node(&self) -> Node {
164        let mut props = Properties::new();
165        props.insert("name".to_string(), PropertyValue::String(self.name.clone()));
166        props.insert(
167            "agent_id".to_string(),
168            PropertyValue::String(self.id.clone()),
169        );
170        props.insert(
171            "status".to_string(),
172            PropertyValue::String(self.status.clone()),
173        );
174
175        let mut node = Node::new("agent", props);
176        // Convert string ID to deterministic UUID for storage
177        node.id = string_to_node_id(&self.id);
178        node
179    }
180
181    pub fn from_node(node: &Node) -> Option<Self> {
182        if node.node_type != "agent" {
183            return None;
184        }
185
186        let name = node.get_property("name").and_then(|v| match v {
187            PropertyValue::String(s) => Some(s.clone()),
188            _ => None,
189        })?;
190
191        // Get agent_id from properties, or convert node.id back to string if not present
192        let id = node
193            .get_property("agent_id")
194            .and_then(|v| match v {
195                PropertyValue::String(s) => Some(s.clone()),
196                _ => None,
197            })
198            .unwrap_or_else(|| node.id.to_string());
199
200        // Get status from properties, default to "offline" if not present
201        let status = node
202            .get_property("status")
203            .and_then(|v| match v {
204                PropertyValue::String(s) => Some(s.clone()),
205                _ => None,
206            })
207            .unwrap_or_else(|| String::from("offline"));
208
209        Some(Self {
210            id,
211            name,
212            status,
213            created_at: node.created_at,
214        })
215    }
216}