cal_redis/cache/
contact.rs

1// File: cal-redis/src/cache/contact.rs
2
3use super::CallableCache;
4use crate::common::{deserialize_from_json, get_items, serialize_to_json};
5use crate::constants::AccountKeys;
6use cal_core::contact::Contact;
7use redis::{AsyncCommands, RedisError};
8use std::sync::Arc;
9
10impl CallableCache {
11    /// Inserts a contact into Redis.
12    ///
13    /// # Arguments
14    /// * `contact` - The contact to insert
15    ///
16    /// # Returns
17    /// * `Result<(), RedisError>` - Ok if successful, or a Redis error
18    pub async fn insert_contact(&self, contact: Contact) -> Result<(), RedisError> {
19        println!(
20            "[CallableCache::insert_contact] Inserting contact: {} for account: {}",
21            contact.id, contact.account.id
22        );
23
24        let mut con = self.redis_connection();
25        let hash_key = AccountKeys::contacts(&contact.account.id);
26        let value = serialize_to_json(&contact)?;
27
28        // Store in account-scoped contacts hash
29        con.hset(&hash_key, &contact.id, value).await?;
30
31        // Also add to contact identifiers for lookup by phone numbers
32        let idents_key = AccountKeys::contact_idents(&contact.account.id);
33
34        // Map primary phone to contact ID
35        con.hset(&idents_key, &contact.primary, &contact.id).await?;
36
37        // Map secondary phone if exists
38        if let Some(ref secondary) = contact.secondary {
39            con.hset(&idents_key, secondary, &contact.id).await?;
40        }
41
42        // Map tertiary phone if exists
43        if let Some(ref tertiary) = contact.tertiary {
44            con.hset(&idents_key, tertiary, &contact.id).await?;
45        }
46
47        // Map email if exists
48        if let Some(ref email) = contact.email {
49            con.hset(&idents_key, email, &contact.id).await?;
50        }
51
52        println!("[CallableCache::insert_contact] Successfully inserted contact");
53        Ok(())
54    }
55
56    /// Retrieves a contact by ID.
57    ///
58    /// # Arguments
59    /// * `account_id` - The account ID
60    /// * `contact_id` - The contact ID
61    ///
62    /// # Returns
63    /// * `Result<Option<Arc<Contact>>, RedisError>` - The contact if found
64    pub async fn get_contact_by_id(
65        &self,
66        account_id: &str,
67        contact_id: &str,
68    ) -> Result<Option<Arc<Contact>>, RedisError> {
69        println!(
70            "[CallableCache::get_contact_by_id] Getting contact {} for account: {}",
71            contact_id, account_id
72        );
73
74        let con = self.redis_connection();
75        let hash_key = AccountKeys::contacts(account_id);
76
77        let value: Option<String> = con.clone().hget(&hash_key, contact_id).await?;
78
79        match value {
80            Some(json_str) => {
81                let contact: Contact = deserialize_from_json(&json_str)?;
82                println!("[CallableCache::get_contact_by_id] Found contact: {}", contact.name);
83                Ok(Some(Arc::new(contact)))
84            }
85            None => {
86                println!("[CallableCache::get_contact_by_id] Contact not found");
87                Ok(None)
88            }
89        }
90    }
91
92    /// Retrieves a contact by identifier (phone number or email).
93    ///
94    /// # Arguments
95    /// * `account_id` - The account ID
96    /// * `identifier` - The phone number or email
97    ///
98    /// # Returns
99    /// * `Result<Option<Arc<Contact>>, RedisError>` - The contact if found
100    pub async fn get_contact_by_ident(
101        &self,
102        account_id: &str,
103        identifier: &str,
104    ) -> Result<Option<Arc<Contact>>, RedisError> {
105        println!(
106            "[CallableCache::get_contact_by_ident] Getting contact by identifier: {} for account: {}",
107            identifier, account_id
108        );
109
110        let con = self.redis_connection();
111        let idents_key = AccountKeys::contact_idents(account_id);
112
113        // First get the contact ID from the identifier
114        let contact_id: Option<String> = con.clone().hget(&idents_key, identifier).await?;
115
116        match contact_id {
117            Some(id) => {
118                // Now get the actual contact
119                self.get_contact_by_id(account_id, &id).await
120            }
121            None => {
122                println!("[CallableCache::get_contact_by_ident] No contact found for identifier");
123                Ok(None)
124            }
125        }
126    }
127
128    /// Retrieves all contacts for an account.
129    ///
130    /// # Arguments
131    /// * `account_id` - The account ID
132    ///
133    /// # Returns
134    /// * `Result<Vec<Arc<Contact>>, RedisError>` - List of contacts
135    pub async fn get_contacts_by_account(
136        &self,
137        account_id: &str,
138    ) -> Result<Vec<Arc<Contact>>, RedisError> {
139        println!(
140            "[CallableCache::get_contacts_by_account] Getting all contacts for account: {}",
141            account_id
142        );
143
144        let con = self.redis_connection();
145        let hash_key = AccountKeys::contacts(account_id);
146
147        let contacts: Vec<Contact> = get_items(con, &hash_key).await?;
148        let arc_contacts: Vec<Arc<Contact>> = contacts.into_iter().map(Arc::new).collect();
149
150        println!(
151            "[CallableCache::get_contacts_by_account] Found {} contacts",
152            arc_contacts.len()
153        );
154        Ok(arc_contacts)
155    }
156
157    /// Retrieves contacts by group.
158    ///
159    /// # Arguments
160    /// * `account_id` - The account ID
161    /// * `group` - The group name
162    ///
163    /// # Returns
164    /// * `Result<Vec<Arc<Contact>>, RedisError>` - List of contacts in the group
165    pub async fn get_contacts_by_group(
166        &self,
167        account_id: &str,
168        group: &str,
169    ) -> Result<Vec<Arc<Contact>>, RedisError> {
170        println!(
171            "[CallableCache::get_contacts_by_group] Getting contacts for group: {} in account: {}",
172            group, account_id
173        );
174
175        let all_contacts = self.get_contacts_by_account(account_id).await?;
176
177        let filtered: Vec<Arc<Contact>> = all_contacts
178            .into_iter()
179            .filter(|contact| contact.is_in_group(group))
180            .collect();
181
182        println!(
183            "[CallableCache::get_contacts_by_group] Found {} contacts in group",
184            filtered.len()
185        );
186        Ok(filtered)
187    }
188
189    /// Deletes a contact from Redis.
190    ///
191    /// # Arguments
192    /// * `account_id` - The account ID
193    /// * `contact_id` - The contact ID
194    ///
195    /// # Returns
196    /// * `Result<bool, RedisError>` - true if deleted, false if not found
197    pub async fn delete_contact(
198        &self,
199        account_id: &str,
200        contact_id: &str,
201    ) -> Result<bool, RedisError> {
202        println!(
203            "[CallableCache::delete_contact] Deleting contact {} from account: {}",
204            contact_id, account_id
205        );
206
207        // First get the contact to remove its identifiers
208        if let Some(contact) = self.get_contact_by_id(account_id, contact_id).await? {
209            let mut con = self.redis_connection();
210            let idents_key = AccountKeys::contact_idents(account_id);
211
212            // Remove all identifiers
213            con.hdel(&idents_key, &contact.primary).await?;
214
215            if let Some(ref secondary) = contact.secondary {
216                con.hdel(&idents_key, secondary).await?;
217            }
218
219            if let Some(ref tertiary) = contact.tertiary {
220                con.hdel(&idents_key, tertiary).await?;
221            }
222
223            if let Some(ref email) = contact.email {
224                con.hdel(&idents_key, email).await?;
225            }
226
227            // Remove the contact itself
228            let hash_key = AccountKeys::contacts(account_id);
229            let deleted: bool = con.hdel(&hash_key, contact_id).await?;
230
231            if deleted {
232                println!("[CallableCache::delete_contact] Successfully deleted contact");
233            }
234
235            Ok(deleted)
236        } else {
237            println!("[CallableCache::delete_contact] Contact not found");
238            Ok(false)
239        }
240    }
241
242    /// Batch insert multiple contacts.
243    ///
244    /// # Arguments
245    /// * `contacts` - Vector of contacts to insert
246    ///
247    /// # Returns
248    /// * `Result<(), RedisError>` - Ok if successful, or a Redis error
249    pub async fn insert_contacts(&self, contacts: Vec<Contact>) -> Result<(), RedisError> {
250        println!(
251            "[CallableCache::insert_contacts] Inserting {} contacts",
252            contacts.len()
253        );
254
255        if contacts.is_empty() {
256            return Ok(());
257        }
258
259        let mut con = self.redis_connection();
260        let mut pipe = redis::pipe();
261
262        // Group contacts by account
263        let mut contacts_by_account: std::collections::HashMap<String, Vec<Contact>> = std::collections::HashMap::new();
264
265        for contact in contacts {
266            contacts_by_account
267                .entry(contact.account.id.clone())
268                .or_insert_with(Vec::new)
269                .push(contact);
270        }
271
272        // Process each account's contacts
273        for (account_id, account_contacts) in contacts_by_account {
274            let hash_key = AccountKeys::contacts(&account_id);
275            let idents_key = AccountKeys::contact_idents(&account_id);
276
277            for contact in account_contacts {
278                let value = serialize_to_json(&contact)?;
279                pipe.hset(&hash_key, &contact.id, &value);
280
281                // Add identifiers
282                pipe.hset(&idents_key, &contact.primary, &contact.id);
283
284                if let Some(ref secondary) = contact.secondary {
285                    pipe.hset(&idents_key, secondary, &contact.id);
286                }
287
288                if let Some(ref tertiary) = contact.tertiary {
289                    pipe.hset(&idents_key, tertiary, &contact.id);
290                }
291
292                if let Some(ref email) = contact.email {
293                    pipe.hset(&idents_key, email, &contact.id);
294                }
295            }
296        }
297
298        pipe.query_async(&mut con).await?;
299
300        println!("[CallableCache::insert_contacts] Successfully inserted all contacts");
301        Ok(())
302    }
303}