This commit is contained in:
kristof 2025-04-04 13:17:40 +02:00
parent 9e8fefbf1f
commit 8595bb3950
16 changed files with 1112 additions and 16 deletions

View File

@ -30,3 +30,7 @@ path = "examples/rhai_demo.rs"
[[bin]] [[bin]]
name = "dbexample2" name = "dbexample2"
path = "src/cmd/dbexample2/main.rs" path = "src/cmd/dbexample2/main.rs"
[[bin]]
name = "dbexample_mcc"
path = "src/cmd/dbexample_mcc/main.rs"

View File

@ -0,0 +1,399 @@
use chrono::{Utc, Duration};
use herodb::db::DBBuilder;
use herodb::models::mcc::{
Calendar, Event,
Email, Attachment, Envelope,
Contact, Message
};
use herodb::models::circle::Circle;
use std::path::PathBuf;
use std::fs;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("DB Example MCC: Mail, Calendar, Contacts with Group Support");
println!("=======================================================");
// Create a temporary directory for the database
let db_path = PathBuf::from("/tmp/dbexample_mcc");
if db_path.exists() {
fs::remove_dir_all(&db_path)?;
}
fs::create_dir_all(&db_path)?;
println!("Database path: {:?}", db_path);
// Create a database instance with our models registered
let db = DBBuilder::new(&db_path)
.register_model::<Calendar>()
.register_model::<Event>()
.register_model::<Email>()
.register_model::<Contact>()
.register_model::<Message>()
.register_model::<Circle>()
.build()?;
println!("\n1. Creating Circles (Groups)");
println!("---------------------------");
// Create circles (groups)
let work_circle = Circle::new(
1,
"Work".to_string(),
"Work-related communications".to_string()
);
let family_circle = Circle::new(
2,
"Family".to_string(),
"Family communications".to_string()
);
let friends_circle = Circle::new(
3,
"Friends".to_string(),
"Friends communications".to_string()
);
// Insert circles
db.set::<Circle>(&work_circle)?;
db.set::<Circle>(&family_circle)?;
db.set::<Circle>(&friends_circle)?;
println!("Created circles:");
println!(" - Circle #{}: {}", work_circle.id, work_circle.name);
println!(" - Circle #{}: {}", family_circle.id, family_circle.name);
println!(" - Circle #{}: {}", friends_circle.id, friends_circle.name);
println!("\n2. Creating Contacts with Group Support");
println!("------------------------------------");
// Create contacts
let mut john = Contact::new(
1,
"John".to_string(),
"Doe".to_string(),
"john.doe@example.com".to_string(),
"work".to_string()
);
john.add_group(work_circle.id);
let mut alice = Contact::new(
2,
"Alice".to_string(),
"Smith".to_string(),
"alice.smith@example.com".to_string(),
"family".to_string()
);
alice.add_group(family_circle.id);
let mut bob = Contact::new(
3,
"Bob".to_string(),
"Johnson".to_string(),
"bob.johnson@example.com".to_string(),
"friends".to_string()
);
bob.add_group(friends_circle.id);
bob.add_group(work_circle.id); // Bob is both a friend and a work contact
// Insert contacts
db.set::<Contact>(&john)?;
db.set::<Contact>(&alice)?;
db.set::<Contact>(&bob)?;
println!("Created contacts:");
println!(" - {}: {} (Groups: {:?})", john.full_name(), john.email, john.groups);
println!(" - {}: {} (Groups: {:?})", alice.full_name(), alice.email, alice.groups);
println!(" - {}: {} (Groups: {:?})", bob.full_name(), bob.email, bob.groups);
println!("\n3. Creating Calendars with Group Support");
println!("-------------------------------------");
// Create calendars
let mut work_calendar = Calendar::new(
1,
"Work Calendar".to_string(),
"Work-related events".to_string()
);
work_calendar.add_group(work_circle.id);
let mut personal_calendar = Calendar::new(
2,
"Personal Calendar".to_string(),
"Personal events".to_string()
);
personal_calendar.add_group(family_circle.id);
personal_calendar.add_group(friends_circle.id);
// Insert calendars
db.set::<Calendar>(&work_calendar)?;
db.set::<Calendar>(&personal_calendar)?;
println!("Created calendars:");
println!(" - {}: {} (Groups: {:?})", work_calendar.id, work_calendar.title, work_calendar.groups);
println!(" - {}: {} (Groups: {:?})", personal_calendar.id, personal_calendar.title, personal_calendar.groups);
println!("\n4. Creating Events with Group Support");
println!("----------------------------------");
// Create events
let now = Utc::now();
let tomorrow = now + Duration::days(1);
let next_week = now + Duration::days(7);
let mut work_meeting = Event::new(
1,
work_calendar.id,
"Team Meeting".to_string(),
"Weekly team sync".to_string(),
"Conference Room A".to_string(),
tomorrow,
tomorrow + Duration::hours(1),
"organizer@example.com".to_string()
);
work_meeting.add_group(work_circle.id);
work_meeting.add_attendee(john.email.clone());
work_meeting.add_attendee(bob.email.clone());
let mut family_dinner = Event::new(
2,
personal_calendar.id,
"Family Dinner".to_string(),
"Weekly family dinner".to_string(),
"Home".to_string(),
next_week,
next_week + Duration::hours(2),
"me@example.com".to_string()
);
family_dinner.add_group(family_circle.id);
family_dinner.add_attendee(alice.email.clone());
// Insert events
db.set::<Event>(&work_meeting)?;
db.set::<Event>(&family_dinner)?;
println!("Created events:");
println!(" - {}: {} on {} (Groups: {:?})",
work_meeting.id,
work_meeting.title,
work_meeting.start_time.format("%Y-%m-%d %H:%M"),
work_meeting.groups
);
println!(" - {}: {} on {} (Groups: {:?})",
family_dinner.id,
family_dinner.title,
family_dinner.start_time.format("%Y-%m-%d %H:%M"),
family_dinner.groups
);
println!("\n5. Creating Emails with Group Support");
println!("----------------------------------");
// Create emails
let mut work_email = Email::new(
1,
101,
1,
"INBOX".to_string(),
"Here are the meeting notes from yesterday's discussion.".to_string()
);
work_email.add_group(work_circle.id);
let work_attachment = Attachment {
filename: "meeting_notes.pdf".to_string(),
content_type: "application/pdf".to_string(),
hash: "abc123def456".to_string(),
size: 1024,
};
work_email.add_attachment(work_attachment);
let work_envelope = Envelope {
date: now.timestamp(),
subject: "Meeting Notes".to_string(),
from: vec!["john.doe@example.com".to_string()],
sender: vec!["john.doe@example.com".to_string()],
reply_to: vec!["john.doe@example.com".to_string()],
to: vec!["me@example.com".to_string()],
cc: vec!["bob.johnson@example.com".to_string()],
bcc: vec![],
in_reply_to: "".to_string(),
message_id: "msg123@example.com".to_string(),
};
work_email.set_envelope(work_envelope);
let mut family_email = Email::new(
2,
102,
2,
"INBOX".to_string(),
"Looking forward to seeing you at dinner next week!".to_string()
);
family_email.add_group(family_circle.id);
let family_envelope = Envelope {
date: now.timestamp(),
subject: "Family Dinner".to_string(),
from: vec!["alice.smith@example.com".to_string()],
sender: vec!["alice.smith@example.com".to_string()],
reply_to: vec!["alice.smith@example.com".to_string()],
to: vec!["me@example.com".to_string()],
cc: vec![],
bcc: vec![],
in_reply_to: "".to_string(),
message_id: "msg456@example.com".to_string(),
};
family_email.set_envelope(family_envelope);
// Insert emails
db.set::<Email>(&work_email)?;
db.set::<Email>(&family_email)?;
println!("Created emails:");
println!(" - From: {}, Subject: {} (Groups: {:?})",
work_email.envelope.as_ref().unwrap().from[0],
work_email.envelope.as_ref().unwrap().subject,
work_email.groups
);
println!(" - From: {}, Subject: {} (Groups: {:?})",
family_email.envelope.as_ref().unwrap().from[0],
family_email.envelope.as_ref().unwrap().subject,
family_email.groups
);
println!("\n6. Creating Messages (Chat) with Group Support");
println!("-----------------------------------------");
// Create messages
let mut work_chat = Message::new(
1,
"thread_work_123".to_string(),
"john.doe@example.com".to_string(),
"Can we move the meeting to 3pm?".to_string()
);
work_chat.add_group(work_circle.id);
work_chat.add_recipient("me@example.com".to_string());
work_chat.add_recipient("bob.johnson@example.com".to_string());
let mut friends_chat = Message::new(
2,
"thread_friends_456".to_string(),
"bob.johnson@example.com".to_string(),
"Are we still on for the game this weekend?".to_string()
);
friends_chat.add_group(friends_circle.id);
friends_chat.add_recipient("me@example.com".to_string());
friends_chat.add_reaction("👍".to_string());
// Insert messages
db.set::<Message>(&work_chat)?;
db.set::<Message>(&friends_chat)?;
println!("Created messages:");
println!(" - From: {}, Content: {} (Groups: {:?})",
work_chat.sender_id,
work_chat.content,
work_chat.groups
);
println!(" - From: {}, Content: {} (Groups: {:?}, Reactions: {:?})",
friends_chat.sender_id,
friends_chat.content,
friends_chat.groups,
friends_chat.meta.reactions
);
println!("\n7. Demonstrating Utility Methods");
println!("------------------------------");
// Filter contacts by group
println!("\nFiltering contacts by work group (ID: {}):", work_circle.id);
let all_contacts = db.list::<Contact>()?;
for contact in all_contacts {
if contact.filter_by_groups(&[work_circle.id]) {
println!(" - {} ({})", contact.full_name(), contact.email);
}
}
// Search emails by subject
println!("\nSearching emails with subject containing 'Meeting':");
let all_emails = db.list::<Email>()?;
for email in all_emails {
if email.search_by_subject("Meeting") {
println!(" - Subject: {}, From: {}",
email.envelope.as_ref().unwrap().subject,
email.envelope.as_ref().unwrap().from[0]
);
}
}
// Get events for a calendar
println!("\nGetting events for Work Calendar (ID: {}):", work_calendar.id);
let all_events = db.list::<Event>()?;
let work_events: Vec<Event> = all_events
.into_iter()
.filter(|event| event.calendar_id == work_calendar.id)
.collect();
for event in work_events {
println!(" - {}: {} on {}",
event.id,
event.title,
event.start_time.format("%Y-%m-%d %H:%M")
);
}
// Get attendee contacts for an event
println!("\nGetting attendee contacts for Team Meeting (ID: {}):", work_meeting.id);
let all_contacts = db.list::<Contact>()?;
let attendee_contacts: Vec<Contact> = all_contacts
.into_iter()
.filter(|contact| work_meeting.attendees.contains(&contact.email))
.collect();
for contact in attendee_contacts {
println!(" - {} ({})", contact.full_name(), contact.email);
}
// Convert email to message
println!("\nConverting work email to message:");
let email_to_message = work_email.to_message(3, "thread_converted_789".to_string());
println!(" - Original Email Subject: {}", work_email.envelope.as_ref().unwrap().subject);
println!(" - Converted Message Content: {}", email_to_message.content.split('\n').next().unwrap_or(""));
println!(" - Converted Message Groups: {:?}", email_to_message.groups);
// Insert the converted message
db.set::<Message>(&email_to_message)?;
println!("\n8. Relationship Management");
println!("------------------------");
// Get the calendar for an event
println!("\nGetting calendar for Family Dinner event (ID: {}):", family_dinner.id);
let event_calendar = db.get::<Calendar>(&family_dinner.calendar_id.to_string())?;
println!(" - Calendar: {} ({})", event_calendar.title, event_calendar.description);
// Get events for a contact
println!("\nGetting events where John Doe is an attendee:");
let all_events = db.list::<Event>()?;
let john_events: Vec<Event> = all_events
.into_iter()
.filter(|event| event.attendees.contains(&john.email))
.collect();
for event in john_events {
println!(" - {}: {} on {}",
event.id,
event.title,
event.start_time.format("%Y-%m-%d %H:%M")
);
}
// Get messages in the same thread
println!("\nGetting all messages in the work chat thread:");
let all_messages = db.list::<Message>()?;
let thread_messages: Vec<Message> = all_messages
.into_iter()
.filter(|message| message.thread_id == work_chat.thread_id)
.collect();
for message in thread_messages {
println!(" - From: {}, Content: {}", message.sender_id, message.content);
}
println!("\nExample completed successfully!");
Ok(())
}

View File

@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::core::{SledModel, Storable}; use crate::db::{SledModel, Storable};
use std::collections::HashMap; use std::collections::HashMap;
/// Role represents the role of a member in a circle /// Role represents the role of a member in a circle

View File

@ -6,4 +6,4 @@ pub use circle::{Circle, Member, Role};
pub use name::{Name, Record, RecordType}; pub use name::{Name, Record, RecordType};
// Re-export database components // Re-export database components
pub use crate::core::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB}; pub use crate::db::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB};

View File

@ -5,5 +5,5 @@ pub mod name;
pub use circle::{Circle, Member, Role}; pub use circle::{Circle, Member, Role};
pub use name::{Name, Record, RecordType}; pub use name::{Name, Record, RecordType};
// Re-export database components from core module // Re-export database components from db module
pub use crate::core::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB}; pub use crate::db::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB};

View File

@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::core::{SledModel, Storable}; use crate::db::{SledModel, Storable};
/// Record types for a DNS record /// Record types for a DNS record
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]

View File

@ -0,0 +1,316 @@
# MCC Models Enhancement Plan
## 1. Current State Analysis
The current MCC module consists of:
- **Mail**: Email, Attachment, Envelope models
- **Calendar**: Calendar model
- **Event**: Event, EventMeta models
- **Contacts**: Contact model
All models implement the `Storable` and `SledModel` traits for database integration.
## 2. Planned Enhancements
### 2.1 Add Group Support to All Models
Add a `groups: Vec<u32>` field to each model to enable linking to multiple groups defined in the Circle module.
### 2.2 Create New Message Model
Create a new `message.rs` file with a Message model for chat functionality:
- Different structure from Email
- Include thread_id, sender_id, content fields
- Include metadata for chat-specific features
- Implement Storable and SledModel traits
### 2.3 Add Utility Methods
Add utility methods to each model for:
- **Filtering/Searching**: Methods to filter by groups, search by content/subject
- **Format Conversion**: Methods to convert between formats (e.g., Email to Message)
- **Relationship Management**: Methods to manage relationships between models
## 3. Implementation Plan
```mermaid
flowchart TD
A[Review Current Models] --> B[Add groups field to all models]
B --> C[Create Message model]
C --> D[Add utility methods]
D --> E[Update mod.rs and lib.rs]
E --> F[Update README.md]
```
### 3.1 Detailed Changes
#### 3.1.1 Mail Model (`mail.rs`)
- Add `groups: Vec<u32>` field to `Email` struct
- Add utility methods:
- `filter_by_groups(groups: &[u32]) -> bool`
- `search_by_subject(query: &str) -> bool`
- `search_by_content(query: &str) -> bool`
- `to_message(&self) -> Message` (conversion method)
#### 3.1.2 Calendar Model (`calendar.rs`)
- Add `groups: Vec<u32>` field to `Calendar` struct
- Add utility methods:
- `filter_by_groups(groups: &[u32]) -> bool`
- `get_events(&self, db: &SledDB<Event>) -> SledDBResult<Vec<Event>>` (relationship method)
#### 3.1.3 Event Model (`event.rs`)
- Add `groups: Vec<u32>` field to `Event` struct
- Add utility methods:
- `filter_by_groups(groups: &[u32]) -> bool`
- `get_calendar(&self, db: &SledDB<Calendar>) -> SledDBResult<Calendar>` (relationship method)
- `get_attendee_contacts(&self, db: &SledDB<Contact>) -> SledDBResult<Vec<Contact>>` (relationship method)
#### 3.1.4 Contacts Model (`contacts.rs`)
- Add `groups: Vec<u32>` field to `Contact` struct
- Add utility methods:
- `filter_by_groups(groups: &[u32]) -> bool`
- `search_by_name(query: &str) -> bool`
- `search_by_email(query: &str) -> bool`
- `get_events(&self, db: &SledDB<Event>) -> SledDBResult<Vec<Event>>` (relationship method)
#### 3.1.5 New Message Model (`message.rs`)
```rust
use serde::{Deserialize, Serialize};
use crate::core::{SledModel, Storable};
use chrono::{DateTime, Utc};
/// MessageStatus represents the status of a message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MessageStatus {
Sent,
Delivered,
Read,
Failed,
}
/// MessageMeta contains metadata for a chat message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessageMeta {
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub status: MessageStatus,
pub is_edited: bool,
pub reactions: Vec<String>,
}
/// Message represents a chat message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub id: u32, // Unique identifier
pub thread_id: String, // Thread/conversation identifier
pub sender_id: String, // Sender identifier
pub recipients: Vec<String>, // List of recipient identifiers
pub content: String, // Message content
pub attachments: Vec<String>, // References to attachments
pub groups: Vec<u32>, // Groups this message belongs to
pub meta: MessageMeta, // Message metadata
}
impl Message {
/// Create a new message
pub fn new(id: u32, thread_id: String, sender_id: String, content: String) -> Self {
let now = Utc::now();
Self {
id,
thread_id,
sender_id,
recipients: Vec::new(),
content,
attachments: Vec::new(),
groups: Vec::new(),
meta: MessageMeta {
created_at: now,
updated_at: now,
status: MessageStatus::Sent,
is_edited: false,
reactions: Vec::new(),
},
}
}
/// Add a recipient to this message
pub fn add_recipient(&mut self, recipient: String) {
self.recipients.push(recipient);
}
/// Add an attachment to this message
pub fn add_attachment(&mut self, attachment: String) {
self.attachments.push(attachment);
}
/// Add a group to this message
pub fn add_group(&mut self, group_id: u32) {
if !self.groups.contains(&group_id) {
self.groups.push(group_id);
}
}
/// Filter by groups
pub fn filter_by_groups(&self, groups: &[u32]) -> bool {
groups.iter().any(|g| self.groups.contains(g))
}
/// Search by content
pub fn search_by_content(&self, query: &str) -> bool {
self.content.to_lowercase().contains(&query.to_lowercase())
}
/// Update message status
pub fn update_status(&mut self, status: MessageStatus) {
self.meta.status = status;
self.meta.updated_at = Utc::now();
}
/// Edit message content
pub fn edit_content(&mut self, new_content: String) {
self.content = new_content;
self.meta.is_edited = true;
self.meta.updated_at = Utc::now();
}
/// Add a reaction to the message
pub fn add_reaction(&mut self, reaction: String) {
self.meta.reactions.push(reaction);
self.meta.updated_at = Utc::now();
}
}
// Implement Storable trait (provides default dump/load)
impl Storable for Message {}
// Implement SledModel trait
impl SledModel for Message {
fn get_id(&self) -> String {
self.id.to_string()
}
fn db_prefix() -> &'static str {
"message"
}
}
```
#### 3.1.6 Update Module Files
Update `mod.rs` and `lib.rs` to include the new Message model.
#### 3.1.7 Update README.md
Update the README.md to include information about the Message model and the new utility methods.
## 4. Data Model Diagram
```mermaid
classDiagram
class Email {
+u32 id
+u32 uid
+u32 seq_num
+String mailbox
+String message
+Vec~Attachment~ attachments
+Vec~String~ flags
+i64 receivetime
+Option~Envelope~ envelope
+Vec~u32~ groups
+filter_by_groups()
+search_by_subject()
+search_by_content()
+to_message()
}
class Calendar {
+u32 id
+String title
+String description
+Vec~u32~ groups
+filter_by_groups()
+get_events()
}
class Event {
+u32 id
+u32 calendar_id
+String title
+String description
+String location
+DateTime start_time
+DateTime end_time
+bool all_day
+String recurrence
+Vec~String~ attendees
+String organizer
+String status
+EventMeta meta
+Vec~u32~ groups
+filter_by_groups()
+get_calendar()
+get_attendee_contacts()
}
class Contact {
+u32 id
+i64 created_at
+i64 modified_at
+String first_name
+String last_name
+String email
+String group
+Vec~u32~ groups
+filter_by_groups()
+search_by_name()
+search_by_email()
+get_events()
}
class Message {
+u32 id
+String thread_id
+String sender_id
+Vec~String~ recipients
+String content
+Vec~String~ attachments
+Vec~u32~ groups
+MessageMeta meta
+filter_by_groups()
+search_by_content()
+update_status()
+edit_content()
+add_reaction()
}
class Circle {
+u32 id
+String name
+String description
+Vec~Member~ members
}
Calendar "1" -- "many" Event: contains
Contact "many" -- "many" Event: attends
Circle "1" -- "many" Email: groups
Circle "1" -- "many" Calendar: groups
Circle "1" -- "many" Event: groups
Circle "1" -- "many" Contact: groups
Circle "1" -- "many" Message: groups
```
## 5. Testing Strategy
1. Unit tests for each model to verify:
- Group field functionality
- New utility methods
- Serialization/deserialization with the new fields
2. Integration tests to verify:
- Database operations with the updated models
- Relationships between models

View File

@ -21,6 +21,14 @@ The Mail models provide email and IMAP functionality:
- **Attachment**: Represents a file attachment with file information - **Attachment**: Represents a file attachment with file information
- **Envelope**: Represents an IMAP envelope structure with message headers - **Envelope**: Represents an IMAP envelope structure with message headers
### Message (`message.rs`)
The Message models provide chat functionality:
- **Message**: Main struct for chat messages with thread and recipient information
- **MessageMeta**: Contains metadata for message status, editing, and reactions
- **MessageStatus**: Enum representing the status of a message (Sent, Delivered, Read, Failed)
### Calendar (`calendar.rs`) ### Calendar (`calendar.rs`)
The Calendar model represents a container for calendar events: The Calendar model represents a container for calendar events:
@ -40,6 +48,31 @@ The Contacts model provides contact management:
- **Contact**: Main struct for contact information with personal details and grouping - **Contact**: Main struct for contact information with personal details and grouping
## Group Support
All models now support linking to multiple groups (Circle IDs):
- Each model has a `groups: Vec<u32>` field to store multiple group IDs
- Utility methods for adding, removing, and filtering by groups
- Groups are defined in the Circle module
## Utility Methods
Each model provides utility methods for:
### Filtering/Searching
- `filter_by_groups(groups: &[u32]) -> bool`: Filter by groups
- `search_by_subject/content/name/email(query: &str) -> bool`: Search by various fields
### Format Conversion
- `to_message()`: Convert Email to Message
### Relationship Management
- `get_events()`: Get events associated with a calendar or contact
- `get_calendar()`: Get the calendar an event belongs to
- `get_attendee_contacts()`: Get contacts for event attendees
- `get_thread_messages()`: Get all messages in the same thread
## Usage ## Usage
These models are used by the MCC module to manage emails, calendar events, and contacts. They are typically accessed through the database handlers that implement the generic SledDB interface. These models are used by the MCC module to manage emails, calendar events, and contacts. They are typically accessed through the database handlers that implement the generic SledDB interface.

View File

@ -1,5 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::core::{SledModel, Storable}; use crate::db::{SledModel, Storable, SledDB, SledDBResult};
use crate::models::mcc::event::Event;
/// Calendar represents a calendar container for events /// Calendar represents a calendar container for events
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -7,6 +8,7 @@ pub struct Calendar {
pub id: u32, // Unique identifier pub id: u32, // Unique identifier
pub title: String, // Calendar title pub title: String, // Calendar title
pub description: String, // Calendar details pub description: String, // Calendar details
pub groups: Vec<u32>, // Groups this calendar belongs to (references Circle IDs)
} }
impl Calendar { impl Calendar {
@ -16,8 +18,37 @@ impl Calendar {
id, id,
title, title,
description, description,
groups: Vec::new(),
} }
} }
/// Add a group to this calendar
pub fn add_group(&mut self, group_id: u32) {
if !self.groups.contains(&group_id) {
self.groups.push(group_id);
}
}
/// Remove a group from this calendar
pub fn remove_group(&mut self, group_id: u32) {
self.groups.retain(|&id| id != group_id);
}
/// Filter by groups - returns true if this calendar belongs to any of the specified groups
pub fn filter_by_groups(&self, groups: &[u32]) -> bool {
groups.iter().any(|g| self.groups.contains(g))
}
/// Get all events associated with this calendar
pub fn get_events(&self, db: &SledDB<Event>) -> SledDBResult<Vec<Event>> {
let all_events = db.list()?;
let calendar_events = all_events
.into_iter()
.filter(|event| event.calendar_id == self.id)
.collect();
Ok(calendar_events)
}
} }
// Implement Storable trait (provides default dump/load) // Implement Storable trait (provides default dump/load)

View File

@ -1,6 +1,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::core::{SledModel, Storable}; use crate::db::{SledModel, Storable, SledDB, SledDBResult};
use chrono::{DateTime, Utc}; use crate::models::mcc::event::Event;
use chrono::Utc;
/// Contact represents a contact entry in an address book /// Contact represents a contact entry in an address book
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -14,6 +15,7 @@ pub struct Contact {
pub last_name: String, pub last_name: String,
pub email: String, pub email: String,
pub group: String, // Reference to a dns name, each group has a globally unique dns pub group: String, // Reference to a dns name, each group has a globally unique dns
pub groups: Vec<u32>, // Groups this contact belongs to (references Circle IDs)
} }
impl Contact { impl Contact {
@ -28,9 +30,49 @@ impl Contact {
last_name, last_name,
email, email,
group, group,
groups: Vec::new(),
} }
} }
/// Add a group to this contact
pub fn add_group(&mut self, group_id: u32) {
if !self.groups.contains(&group_id) {
self.groups.push(group_id);
}
}
/// Remove a group from this contact
pub fn remove_group(&mut self, group_id: u32) {
self.groups.retain(|&id| id != group_id);
}
/// Filter by groups - returns true if this contact belongs to any of the specified groups
pub fn filter_by_groups(&self, groups: &[u32]) -> bool {
groups.iter().any(|g| self.groups.contains(g))
}
/// Search by name - returns true if the name contains the query (case-insensitive)
pub fn search_by_name(&self, query: &str) -> bool {
let full_name = self.full_name().to_lowercase();
query.to_lowercase().split_whitespace().all(|word| full_name.contains(word))
}
/// Search by email - returns true if the email contains the query (case-insensitive)
pub fn search_by_email(&self, query: &str) -> bool {
self.email.to_lowercase().contains(&query.to_lowercase())
}
/// Get events where this contact is an attendee
pub fn get_events(&self, db: &SledDB<Event>) -> SledDBResult<Vec<Event>> {
let all_events = db.list()?;
let contact_events = all_events
.into_iter()
.filter(|event| event.attendees.contains(&self.email))
.collect();
Ok(contact_events)
}
/// Update the contact's information /// Update the contact's information
pub fn update(&mut self, first_name: Option<String>, last_name: Option<String>, email: Option<String>, group: Option<String>) { pub fn update(&mut self, first_name: Option<String>, last_name: Option<String>, email: Option<String>, group: Option<String>) {
if let Some(first_name) = first_name { if let Some(first_name) = first_name {
@ -52,6 +94,12 @@ impl Contact {
self.modified_at = Utc::now().timestamp(); self.modified_at = Utc::now().timestamp();
} }
/// Update the contact's groups
pub fn update_groups(&mut self, groups: Vec<u32>) {
self.groups = groups;
self.modified_at = Utc::now().timestamp();
}
/// Get the full name of the contact /// Get the full name of the contact
pub fn full_name(&self) -> String { pub fn full_name(&self) -> String {
format!("{} {}", self.first_name, self.last_name) format!("{} {}", self.first_name, self.last_name)

View File

@ -1,5 +1,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::core::{SledModel, Storable}; use crate::db::{SledModel, Storable, SledDB, SledDBResult};
use crate::models::mcc::calendar::Calendar;
use crate::models::mcc::contacts::Contact;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
/// EventMeta contains additional metadata for a calendar event /// EventMeta contains additional metadata for a calendar event
@ -27,6 +29,7 @@ pub struct Event {
pub organizer: String, // Organizer email pub organizer: String, // Organizer email
pub status: String, // "CONFIRMED", "CANCELLED", "TENTATIVE" pub status: String, // "CONFIRMED", "CANCELLED", "TENTATIVE"
pub meta: EventMeta, // Additional metadata pub meta: EventMeta, // Additional metadata
pub groups: Vec<u32>, // Groups this event belongs to (references Circle IDs)
} }
impl Event { impl Event {
@ -60,9 +63,43 @@ impl Event {
etag: String::new(), etag: String::new(),
color: String::new(), color: String::new(),
}, },
groups: Vec::new(),
} }
} }
/// Add a group to this event
pub fn add_group(&mut self, group_id: u32) {
if !self.groups.contains(&group_id) {
self.groups.push(group_id);
}
}
/// Remove a group from this event
pub fn remove_group(&mut self, group_id: u32) {
self.groups.retain(|&id| id != group_id);
}
/// Filter by groups - returns true if this event belongs to any of the specified groups
pub fn filter_by_groups(&self, groups: &[u32]) -> bool {
groups.iter().any(|g| self.groups.contains(g))
}
/// Get the calendar this event belongs to
pub fn get_calendar(&self, db: &SledDB<Calendar>) -> SledDBResult<Calendar> {
db.get(&self.calendar_id.to_string())
}
/// Get contacts for all attendees of this event
pub fn get_attendee_contacts(&self, db: &SledDB<Contact>) -> SledDBResult<Vec<Contact>> {
let all_contacts = db.list()?;
let attendee_contacts = all_contacts
.into_iter()
.filter(|contact| self.attendees.contains(&contact.email))
.collect();
Ok(attendee_contacts)
}
/// Add an attendee to this event /// Add an attendee to this event
pub fn add_attendee(&mut self, attendee: String) { pub fn add_attendee(&mut self, attendee: String) {
self.attendees.push(attendee); self.attendees.push(attendee);
@ -77,6 +114,16 @@ impl Event {
pub fn set_status(&mut self, status: &str) { pub fn set_status(&mut self, status: &str) {
self.status = status.to_string(); self.status = status.to_string();
} }
/// Search by title - returns true if the title contains the query (case-insensitive)
pub fn search_by_title(&self, query: &str) -> bool {
self.title.to_lowercase().contains(&query.to_lowercase())
}
/// Search by description - returns true if the description contains the query (case-insensitive)
pub fn search_by_description(&self, query: &str) -> bool {
self.description.to_lowercase().contains(&query.to_lowercase())
}
} }
// Implement Storable trait (provides default dump/load) // Implement Storable trait (provides default dump/load)

View File

@ -2,12 +2,14 @@ pub mod calendar;
pub mod event; pub mod event;
pub mod mail; pub mod mail;
pub mod contacts; pub mod contacts;
pub mod message;
// Re-export all model types for convenience // Re-export all model types for convenience
pub use calendar::Calendar; pub use calendar::Calendar;
pub use event::{Event, EventMeta}; pub use event::{Event, EventMeta};
pub use mail::{Email, Attachment, Envelope}; pub use mail::{Email, Attachment, Envelope};
pub use contacts::Contact; pub use contacts::Contact;
pub use message::{Message, MessageMeta, MessageStatus};
// Re-export database components from core module // Re-export database components from db module
pub use crate::core::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB}; pub use crate::db::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB};

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::core::{SledModel, Storable}; use crate::db::{SledModel, Storable, SledDBResult, SledDB};
use chrono::{DateTime, Utc}; use chrono::Utc;
/// Email represents an email message with all its metadata and content /// Email represents an email message with all its metadata and content
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -17,6 +17,7 @@ pub struct Email {
pub flags: Vec<String>, // IMAP flags like \Seen, \Deleted, etc. pub flags: Vec<String>, // IMAP flags like \Seen, \Deleted, etc.
pub receivetime: i64, // Unix timestamp when the email was received pub receivetime: i64, // Unix timestamp when the email was received
pub envelope: Option<Envelope>, // IMAP envelope information (contains From, To, Subject, etc.) pub envelope: Option<Envelope>, // IMAP envelope information (contains From, To, Subject, etc.)
pub groups: Vec<u32>, // Groups this email belongs to (references Circle IDs)
} }
/// Attachment represents an email attachment /// Attachment represents an email attachment
@ -56,6 +57,7 @@ impl Email {
flags: Vec::new(), flags: Vec::new(),
receivetime: chrono::Utc::now().timestamp(), receivetime: chrono::Utc::now().timestamp(),
envelope: None, envelope: None,
groups: Vec::new(),
} }
} }
@ -64,10 +66,86 @@ impl Email {
self.attachments.push(attachment); self.attachments.push(attachment);
} }
/// Add a group to this email
pub fn add_group(&mut self, group_id: u32) {
if !self.groups.contains(&group_id) {
self.groups.push(group_id);
}
}
/// Remove a group from this email
pub fn remove_group(&mut self, group_id: u32) {
self.groups.retain(|&id| id != group_id);
}
/// Filter by groups - returns true if this email belongs to any of the specified groups
pub fn filter_by_groups(&self, groups: &[u32]) -> bool {
groups.iter().any(|g| self.groups.contains(g))
}
/// Search by subject - returns true if the subject contains the query (case-insensitive)
pub fn search_by_subject(&self, query: &str) -> bool {
if let Some(env) = &self.envelope {
env.subject.to_lowercase().contains(&query.to_lowercase())
} else {
false
}
}
/// Search by content - returns true if the message content contains the query (case-insensitive)
pub fn search_by_content(&self, query: &str) -> bool {
self.message.to_lowercase().contains(&query.to_lowercase())
}
/// Set the envelope for this email /// Set the envelope for this email
pub fn set_envelope(&mut self, envelope: Envelope) { pub fn set_envelope(&mut self, envelope: Envelope) {
self.envelope = Some(envelope); self.envelope = Some(envelope);
} }
/// Convert this email to a Message (for chat)
pub fn to_message(&self, id: u32, thread_id: String) -> crate::models::mcc::message::Message {
use crate::models::mcc::message::Message;
let now = Utc::now();
let sender = if let Some(env) = &self.envelope {
if !env.from.is_empty() {
env.from[0].clone()
} else {
"unknown@example.com".to_string()
}
} else {
"unknown@example.com".to_string()
};
let subject = if let Some(env) = &self.envelope {
env.subject.clone()
} else {
"No Subject".to_string()
};
let recipients = if let Some(env) = &self.envelope {
env.to.clone()
} else {
Vec::new()
};
let content = if !subject.is_empty() {
format!("{}\n\n{}", subject, self.message)
} else {
self.message.clone()
};
let mut message = Message::new(id, thread_id, sender, content);
message.recipients = recipients;
message.groups = self.groups.clone();
// Convert attachments to references
for attachment in &self.attachments {
message.add_attachment(attachment.filename.clone());
}
message
}
} }
// Implement Storable trait (provides default dump/load) // Implement Storable trait (provides default dump/load)

View File

@ -0,0 +1,134 @@
use serde::{Deserialize, Serialize};
use crate::db::{SledModel, Storable, SledDB, SledDBResult};
use chrono::{DateTime, Utc};
/// MessageStatus represents the status of a message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MessageStatus {
Sent,
Delivered,
Read,
Failed,
}
/// MessageMeta contains metadata for a chat message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessageMeta {
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub status: MessageStatus,
pub is_edited: bool,
pub reactions: Vec<String>,
}
/// Message represents a chat message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub id: u32, // Unique identifier
pub thread_id: String, // Thread/conversation identifier
pub sender_id: String, // Sender identifier
pub recipients: Vec<String>, // List of recipient identifiers
pub content: String, // Message content
pub attachments: Vec<String>, // References to attachments
pub groups: Vec<u32>, // Groups this message belongs to (references Circle IDs)
pub meta: MessageMeta, // Message metadata
}
impl Message {
/// Create a new message
pub fn new(id: u32, thread_id: String, sender_id: String, content: String) -> Self {
let now = Utc::now();
Self {
id,
thread_id,
sender_id,
recipients: Vec::new(),
content,
attachments: Vec::new(),
groups: Vec::new(),
meta: MessageMeta {
created_at: now,
updated_at: now,
status: MessageStatus::Sent,
is_edited: false,
reactions: Vec::new(),
},
}
}
/// Add a recipient to this message
pub fn add_recipient(&mut self, recipient: String) {
self.recipients.push(recipient);
}
/// Add an attachment to this message
pub fn add_attachment(&mut self, attachment: String) {
self.attachments.push(attachment);
}
/// Add a group to this message
pub fn add_group(&mut self, group_id: u32) {
if !self.groups.contains(&group_id) {
self.groups.push(group_id);
}
}
/// Remove a group from this message
pub fn remove_group(&mut self, group_id: u32) {
self.groups.retain(|&id| id != group_id);
}
/// Filter by groups - returns true if this message belongs to any of the specified groups
pub fn filter_by_groups(&self, groups: &[u32]) -> bool {
groups.iter().any(|g| self.groups.contains(g))
}
/// Search by content - returns true if the content contains the query (case-insensitive)
pub fn search_by_content(&self, query: &str) -> bool {
self.content.to_lowercase().contains(&query.to_lowercase())
}
/// Update message status
pub fn update_status(&mut self, status: MessageStatus) {
self.meta.status = status;
self.meta.updated_at = Utc::now();
}
/// Edit message content
pub fn edit_content(&mut self, new_content: String) {
self.content = new_content;
self.meta.is_edited = true;
self.meta.updated_at = Utc::now();
}
/// Add a reaction to the message
pub fn add_reaction(&mut self, reaction: String) {
self.meta.reactions.push(reaction);
self.meta.updated_at = Utc::now();
}
/// Get all messages in the same thread
pub fn get_thread_messages(&self, db: &SledDB<Message>) -> SledDBResult<Vec<Message>> {
let all_messages = db.list()?;
let thread_messages = all_messages
.into_iter()
.filter(|msg| msg.thread_id == self.thread_id)
.collect();
Ok(thread_messages)
}
}
// Implement Storable trait (provides default dump/load)
impl Storable for Message {}
// Implement SledModel trait
impl SledModel for Message {
fn get_id(&self) -> String {
self.id.to_string()
}
fn db_prefix() -> &'static str {
"message"
}
}

View File

@ -2,12 +2,14 @@ pub mod calendar;
pub mod event; pub mod event;
pub mod mail; pub mod mail;
pub mod contacts; pub mod contacts;
pub mod message;
// Re-export all model types for convenience // Re-export all model types for convenience
pub use calendar::Calendar; pub use calendar::Calendar;
pub use event::{Event, EventMeta}; pub use event::{Event, EventMeta};
pub use mail::{Email, Attachment, Envelope}; pub use mail::{Email, Attachment, Envelope};
pub use contacts::Contact; pub use contacts::Contact;
pub use message::{Message, MessageMeta, MessageStatus};
// Re-export database components from core module // Re-export database components from db module
pub use crate::core::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB}; pub use crate::db::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB};

View File

@ -1 +1,3 @@
pub mod biz; pub mod biz;
pub mod mcc;
pub mod circle;