saorsa_core/chat/
mod.rs

1//! Chat system (Slack-like) with channels, threads, and real-time messaging
2//! 
3//! Features:
4//! - Public and private channels with threshold access control
5//! - Direct messages with E2E encryption
6//! - Threaded conversations
7//! - Voice/video calls via WebRTC
8//! - Rich media support
9
10use crate::identity::enhanced::{EnhancedIdentity, OrganizationId};
11use crate::storage::{StorageManager, keys, ttl};
12// Removed unused ThresholdGroup import
13use crate::quantum_crypto::types::GroupId;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::time::SystemTime;
17use thiserror::Error;
18use uuid::Uuid;
19
20/// Chat errors
21#[derive(Debug, Error)]
22pub enum ChatError {
23    #[error("Storage error: {0}")]
24    StorageError(#[from] crate::storage::StorageError),
25    
26    #[error("Channel not found: {0}")]
27    ChannelNotFound(String),
28    
29    #[error("Permission denied: {0}")]
30    PermissionDenied(String),
31    
32    #[error("Invalid operation: {0}")]
33    InvalidOperation(String),
34}
35
36type Result<T> = std::result::Result<T, ChatError>;
37
38/// Channel identifier
39#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
40pub struct ChannelId(pub String);
41
42impl ChannelId {
43    /// Generate new channel ID
44    pub fn new() -> Self {
45        Self(Uuid::new_v4().to_string())
46    }
47}
48
49/// Message identifier
50#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
51pub struct MessageId(pub String);
52
53impl MessageId {
54    /// Generate new message ID
55    pub fn new() -> Self {
56        Self(Uuid::new_v4().to_string())
57    }
58}
59
60/// Thread identifier
61#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
62pub struct ThreadId(pub String);
63
64/// User ID type
65pub type UserId = String;
66
67/// Channel type
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub enum ChannelType {
70    /// Public channel - anyone in org can join
71    Public,
72    
73    /// Private channel - controlled by threshold group
74    Private {
75        access_group: GroupId,
76        visibility: ChannelVisibility,
77    },
78    
79    /// Direct message between users
80    Direct {
81        participants: Vec<UserId>,
82    },
83    
84    /// Group DM with multiple users
85    GroupDirect {
86        participants: Vec<UserId>,
87        name: Option<String>,
88    },
89}
90
91/// Channel visibility for private channels
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub enum ChannelVisibility {
94    /// Visible to all, but join requires approval
95    Listed,
96    
97    /// Not visible unless member
98    Hidden,
99    
100    /// Invite-only
101    Secret,
102}
103
104/// Channel information
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct Channel {
107    pub id: ChannelId,
108    pub name: String,
109    pub description: String,
110    pub channel_type: ChannelType,
111    pub organization_id: Option<OrganizationId>,
112    pub created_by: UserId,
113    pub created_at: SystemTime,
114    pub members: Vec<ChannelMember>,
115    pub settings: ChannelSettings,
116    pub metadata: HashMap<String, String>,
117}
118
119/// Channel member
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct ChannelMember {
122    pub user_id: UserId,
123    pub role: ChannelRole,
124    pub joined_at: SystemTime,
125    pub last_read: Option<MessageId>,
126    pub notifications: NotificationSettings,
127}
128
129/// Channel role
130#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
131pub enum ChannelRole {
132    Owner,
133    Admin,
134    Moderator,
135    Member,
136    Guest,
137}
138
139/// Notification settings
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct NotificationSettings {
142    pub all_messages: bool,
143    pub mentions_only: bool,
144    pub muted: bool,
145    pub muted_until: Option<SystemTime>,
146}
147
148impl Default for NotificationSettings {
149    fn default() -> Self {
150        Self {
151            all_messages: true,
152            mentions_only: false,
153            muted: false,
154            muted_until: None,
155        }
156    }
157}
158
159/// Channel settings
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct ChannelSettings {
162    pub allow_threads: bool,
163    pub allow_reactions: bool,
164    pub allow_files: bool,
165    pub allow_voice_video: bool,
166    pub message_retention_days: Option<u32>,
167    pub max_message_length: usize,
168    pub slow_mode_seconds: Option<u32>,
169}
170
171impl Default for ChannelSettings {
172    fn default() -> Self {
173        Self {
174            allow_threads: true,
175            allow_reactions: true,
176            allow_files: true,
177            allow_voice_video: true,
178            message_retention_days: Some(90),
179            max_message_length: 4000,
180            slow_mode_seconds: None,
181        }
182    }
183}
184
185/// Message content
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct Message {
188    pub id: MessageId,
189    pub channel_id: ChannelId,
190    pub thread_id: Option<ThreadId>,
191    pub author: UserId,
192    pub content: MessageContent,
193    pub created_at: SystemTime,
194    pub edited_at: Option<SystemTime>,
195    pub deleted_at: Option<SystemTime>,
196    pub reactions: Vec<Reaction>,
197    pub mentions: Vec<Mention>,
198    pub attachments: Vec<Attachment>,
199    pub reply_to: Option<MessageId>,
200}
201
202/// Message content type
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub enum MessageContent {
205    /// Plain text message
206    Text(String),
207    
208    /// Rich text with formatting
209    RichText {
210        text: String,
211        formatting: Vec<TextFormat>,
212    },
213    
214    /// System message (join/leave/etc)
215    System(SystemMessage),
216    
217    /// Encrypted content (for DMs)
218    Encrypted {
219        ciphertext: Vec<u8>,
220        algorithm: String,
221    },
222}
223
224/// Text formatting
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct TextFormat {
227    pub start: usize,
228    pub end: usize,
229    pub format_type: FormatType,
230}
231
232/// Format type
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub enum FormatType {
235    Bold,
236    Italic,
237    Code,
238    Strike,
239    Link(String),
240    Mention(UserId),
241    ChannelRef(ChannelId),
242}
243
244/// System message type
245#[derive(Debug, Clone, Serialize, Deserialize)]
246pub enum SystemMessage {
247    UserJoined(UserId),
248    UserLeft(UserId),
249    UserInvited { inviter: UserId, invitee: UserId },
250    ChannelRenamed { old_name: String, new_name: String },
251    ChannelDescriptionChanged,
252    CallStarted { call_id: String },
253    CallEnded { call_id: String, duration_seconds: u64 },
254}
255
256/// Reaction to a message
257#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct Reaction {
259    pub emoji: String,
260    pub users: Vec<UserId>,
261}
262
263/// Mention in a message
264#[derive(Debug, Clone, Serialize, Deserialize)]
265pub enum Mention {
266    User(UserId),
267    Channel,
268    Here,
269    Everyone,
270}
271
272/// File attachment
273#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct Attachment {
275    pub id: String,
276    pub name: String,
277    pub mime_type: String,
278    pub size: u64,
279    pub url: String,
280    pub thumbnail_url: Option<String>,
281    pub metadata: AttachmentMetadata,
282}
283
284/// Attachment metadata
285#[derive(Debug, Clone, Serialize, Deserialize)]
286pub enum AttachmentMetadata {
287    Image { width: u32, height: u32 },
288    Video { duration_seconds: u64, width: u32, height: u32 },
289    Audio { duration_seconds: u64 },
290    Document { page_count: Option<u32> },
291    Other,
292}
293
294/// Thread information
295#[derive(Debug, Clone, Serialize, Deserialize)]
296pub struct Thread {
297    pub id: ThreadId,
298    pub channel_id: ChannelId,
299    pub parent_message_id: MessageId,
300    pub reply_count: u32,
301    pub participant_count: u32,
302    pub last_reply_at: Option<SystemTime>,
303    pub participants: Vec<UserId>,
304}
305
306/// Voice/video call information
307#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct Call {
309    pub id: String,
310    pub channel_id: ChannelId,
311    pub call_type: CallType,
312    pub started_by: UserId,
313    pub started_at: SystemTime,
314    pub ended_at: Option<SystemTime>,
315    pub participants: Vec<CallParticipant>,
316    pub recording_url: Option<String>,
317}
318
319/// Call type
320#[derive(Debug, Clone, Serialize, Deserialize)]
321pub enum CallType {
322    Voice,
323    Video,
324    ScreenShare,
325}
326
327/// Call participant
328#[derive(Debug, Clone, Serialize, Deserialize)]
329pub struct CallParticipant {
330    pub user_id: UserId,
331    pub joined_at: SystemTime,
332    pub left_at: Option<SystemTime>,
333    pub is_muted: bool,
334    pub is_video_on: bool,
335    pub is_screen_sharing: bool,
336}
337
338/// Chat manager
339pub struct ChatManager {
340    storage: StorageManager,
341    identity: EnhancedIdentity,
342}
343
344impl ChatManager {
345    /// Create new chat manager
346    pub fn new(storage: StorageManager, identity: EnhancedIdentity) -> Self {
347        Self { storage, identity }
348    }
349    
350    /// Create a new channel
351    pub async fn create_channel(
352        &mut self,
353        name: String,
354        description: String,
355        channel_type: ChannelType,
356        organization_id: Option<OrganizationId>,
357    ) -> Result<Channel> {
358        // Check permissions
359        if let Some(org_id) = &organization_id {
360            // Verify user has permission to create channels in org
361            // TODO: Implement permission check
362        }
363        
364        let channel = Channel {
365            id: ChannelId::new(),
366            name,
367            description,
368            channel_type,
369            organization_id,
370            created_by: self.identity.base_identity.user_id.clone(),
371            created_at: SystemTime::now(),
372            members: vec![ChannelMember {
373                user_id: self.identity.base_identity.user_id.clone(),
374                role: ChannelRole::Owner,
375                joined_at: SystemTime::now(),
376                last_read: None,
377                notifications: NotificationSettings::default(),
378            }],
379            settings: ChannelSettings::default(),
380            metadata: HashMap::new(),
381        };
382        
383        // Store channel in DHT
384        let key = keys::chat_channel(&channel.id.0);
385        self.storage.store_encrypted(
386            &key,
387            &channel,
388            ttl::PROFILE,
389            None,
390        ).await?;
391        
392        // Add to user's channel list
393        self.add_user_channel(&channel.id).await?;
394        
395        // If public, add to public channel list
396        if matches!(channel.channel_type, ChannelType::Public) {
397            self.add_public_channel(&channel.id, &channel.name).await?;
398        }
399        
400        Ok(channel)
401    }
402    
403    /// Send a message
404    pub async fn send_message(
405        &mut self,
406        channel_id: &ChannelId,
407        content: MessageContent,
408        thread_id: Option<ThreadId>,
409        attachments: Vec<Attachment>,
410    ) -> Result<Message> {
411        // Verify user is member of channel
412        let channel = self.get_channel(channel_id).await?;
413        if !channel.members.iter().any(|m| m.user_id == self.identity.base_identity.user_id) {
414            return Err(ChatError::PermissionDenied("Not a member of channel".to_string()));
415        }
416        
417        let message = Message {
418            id: MessageId::new(),
419            channel_id: channel_id.clone(),
420            thread_id,
421            author: self.identity.base_identity.user_id.clone(),
422            content,
423            created_at: SystemTime::now(),
424            edited_at: None,
425            deleted_at: None,
426            reactions: vec![],
427            mentions: vec![], // TODO: Extract mentions from content
428            attachments,
429            reply_to: None,
430        };
431        
432        // Store message
433        let msg_key = keys::chat_message(&channel_id.0, &message.id.0);
434        self.storage.store_encrypted(
435            &msg_key,
436            &message,
437            ttl::MESSAGE,
438            None,
439        ).await?;
440        
441        // Add to message index for pagination
442        let timestamp = message.created_at.duration_since(SystemTime::UNIX_EPOCH)
443            .unwrap().as_secs();
444        let index_key = keys::chat_index(&channel_id.0, timestamp);
445        self.storage.store_encrypted(
446            &index_key,
447            &message.id,
448            ttl::MESSAGE,
449            None,
450        ).await?;
451        
452        Ok(message)
453    }
454    
455    /// Get channel by ID
456    pub async fn get_channel(&self, channel_id: &ChannelId) -> Result<Channel> {
457        let key = keys::chat_channel(&channel_id.0);
458        self.storage.get_encrypted(&key).await
459            .map_err(|_| ChatError::ChannelNotFound(channel_id.0.clone()))
460    }
461    
462    /// Get user's channels
463    pub async fn get_user_channels(&self) -> Result<Vec<ChannelId>> {
464        let key = keys::user_channels(&self.identity.base_identity.user_id);
465        Ok(self.storage.get_encrypted(&key).await
466            .unwrap_or_else(|_| vec![]))
467    }
468    
469    /// Add channel to user's list
470    async fn add_user_channel(&mut self, channel_id: &ChannelId) -> Result<()> {
471        let mut channels = self.get_user_channels().await.unwrap_or_default();
472        if !channels.contains(channel_id) {
473            channels.push(channel_id.clone());
474            
475            let key = keys::user_channels(&self.identity.base_identity.user_id);
476            self.storage.store_encrypted(
477                &key,
478                &channels,
479                ttl::PROFILE,
480                None,
481            ).await?;
482        }
483        Ok(())
484    }
485    
486    /// Add channel to public list
487    async fn add_public_channel(&mut self, channel_id: &ChannelId, name: &str) -> Result<()> {
488        let key = keys::public_channel_list();
489        let mut public_channels: HashMap<String, String> = self.storage
490            .get_public(&key).await
491            .unwrap_or_default();
492        
493        public_channels.insert(channel_id.0.clone(), name.to_string());
494        
495        self.storage.store_public(
496            &key,
497            &public_channels,
498            ttl::PROFILE,
499        ).await?;
500        
501        Ok(())
502    }
503    
504    /// Create a thread
505    pub async fn create_thread(
506        &mut self,
507        channel_id: &ChannelId,
508        parent_message_id: &MessageId,
509    ) -> Result<Thread> {
510        let thread = Thread {
511            id: ThreadId(Uuid::new_v4().to_string()),
512            channel_id: channel_id.clone(),
513            parent_message_id: parent_message_id.clone(),
514            reply_count: 0,
515            participant_count: 1,
516            last_reply_at: None,
517            participants: vec![self.identity.base_identity.user_id.clone()],
518        };
519        
520        // Store thread (in practice, would be part of message structure)
521        Ok(thread)
522    }
523    
524    /// Add reaction to message
525    pub async fn add_reaction(
526        &mut self,
527        channel_id: &ChannelId,
528        message_id: &MessageId,
529        emoji: String,
530    ) -> Result<()> {
531        let msg_key = keys::chat_message(&channel_id.0, &message_id.0);
532        let mut message: Message = self.storage.get_encrypted(&msg_key).await?;
533        
534        // Find or create reaction
535        let user_id = &self.identity.base_identity.user_id;
536        if let Some(reaction) = message.reactions.iter_mut().find(|r| r.emoji == emoji) {
537            if !reaction.users.contains(user_id) {
538                reaction.users.push(user_id.clone());
539            }
540        } else {
541            message.reactions.push(Reaction {
542                emoji,
543                users: vec![user_id.clone()],
544            });
545        }
546        
547        // Store updated message
548        self.storage.store_encrypted(
549            &msg_key,
550            &message,
551            ttl::MESSAGE,
552            None,
553        ).await?;
554        
555        Ok(())
556    }
557}