Skip to main content

saorsa_core/chat/
mod.rs

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