Complete frontend migration to shared VEvent model

- Update frontend to use shared calendar-models library
- Add display methods to VEvent for UI compatibility
- Remove duplicate VEvent definitions in frontend
- Fix JSON field name mismatch (dtstart/dtend vs start/end)
- Create CalendarEvent type alias for backward compatibility
- Resolve "missing field start" parsing error

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Connor Johnstone
2025-08-30 11:58:37 -04:00
parent 15f2d0c6d9
commit 663b322d97
4 changed files with 44 additions and 904 deletions

View File

@@ -146,4 +146,38 @@ impl VEvent {
pub fn is_exception(&self) -> bool {
self.recurrence_id.is_some()
}
/// Get display string for status
pub fn get_status_display(&self) -> &'static str {
match &self.status {
Some(EventStatus::Tentative) => "Tentative",
Some(EventStatus::Confirmed) => "Confirmed",
Some(EventStatus::Cancelled) => "Cancelled",
None => "Confirmed", // Default
}
}
/// Get display string for class
pub fn get_class_display(&self) -> &'static str {
match &self.class {
Some(EventClass::Public) => "Public",
Some(EventClass::Private) => "Private",
Some(EventClass::Confidential) => "Confidential",
None => "Public", // Default
}
}
/// Get display string for priority
pub fn get_priority_display(&self) -> String {
match self.priority {
None => "Not set".to_string(),
Some(0) => "Undefined".to_string(),
Some(1) => "High".to_string(),
Some(p) if p <= 4 => "High".to_string(),
Some(5) => "Medium".to_string(),
Some(p) if p <= 8 => "Low".to_string(),
Some(9) => "Low".to_string(),
Some(p) => format!("Priority {}", p),
}
}
}

View File

@@ -159,7 +159,7 @@ pub fn calendar_view(props: &CalendarViewProps) -> Html {
match calendar_service.refresh_event(&token, &password, &uid).await {
Ok(Some(refreshed_event)) => {
let refreshed_vevent = VEvent::from_calendar_event(&refreshed_event);
let refreshed_vevent = refreshed_event; // CalendarEvent is now VEvent
let mut updated_events = (*events).clone();
for (_, day_events) in updated_events.iter_mut() {

View File

@@ -1,841 +1,2 @@
// RFC 5545 Compliant iCalendar Data Structures
// This file contains updated structures that fully comply with RFC 5545 iCalendar specification
use chrono::{DateTime, Utc, Duration};
use serde::{Deserialize, Serialize};
// ==================== CALENDAR OBJECT (VCALENDAR) ====================
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ICalendarObject {
// Required calendar properties
pub prodid: String, // Product identifier (PRODID)
pub version: String, // Version (typically "2.0")
// Optional calendar properties
pub calscale: Option<String>, // Calendar scale (CALSCALE) - default "GREGORIAN"
pub method: Option<String>, // Method (METHOD)
// Components
pub events: Vec<VEvent>, // VEVENT components
pub todos: Vec<VTodo>, // VTODO components
pub journals: Vec<VJournal>, // VJOURNAL components
pub freebusys: Vec<VFreeBusy>, // VFREEBUSY components
pub timezones: Vec<VTimeZone>, // VTIMEZONE components
}
// ==================== VEVENT COMPONENT ====================
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct VEvent {
// Required properties
pub dtstamp: DateTime<Utc>, // Date-time stamp (DTSTAMP) - REQUIRED
pub uid: String, // Unique identifier (UID) - REQUIRED
pub dtstart: DateTime<Utc>, // Start date-time (DTSTART) - REQUIRED
// Optional properties (commonly used)
pub dtend: Option<DateTime<Utc>>, // End date-time (DTEND)
pub duration: Option<Duration>, // Duration (DURATION) - alternative to DTEND
pub summary: Option<String>, // Summary/title (SUMMARY)
pub description: Option<String>, // Description (DESCRIPTION)
pub location: Option<String>, // Location (LOCATION)
// Classification and status
pub class: Option<EventClass>, // Classification (CLASS)
pub status: Option<EventStatus>, // Status (STATUS)
pub transp: Option<TimeTransparency>, // Time transparency (TRANSP)
pub priority: Option<u8>, // Priority 0-9 (PRIORITY)
// People and organization
pub organizer: Option<CalendarUser>, // Organizer (ORGANIZER)
pub attendees: Vec<Attendee>, // Attendees (ATTENDEE)
pub contact: Option<String>, // Contact information (CONTACT)
// Categorization and relationships
pub categories: Vec<String>, // Categories (CATEGORIES)
pub comment: Option<String>, // Comment (COMMENT)
pub resources: Vec<String>, // Resources (RESOURCES)
pub related_to: Option<String>, // Related component (RELATED-TO)
pub url: Option<String>, // URL (URL)
// Geographical
pub geo: Option<GeographicPosition>, // Geographic position (GEO)
// Versioning and modification
pub sequence: Option<u32>, // Sequence number (SEQUENCE)
pub created: Option<DateTime<Utc>>, // Creation time (CREATED)
pub last_modified: Option<DateTime<Utc>>, // Last modified (LAST-MODIFIED)
// Recurrence
pub rrule: Option<String>, // Recurrence rule (RRULE)
pub rdate: Vec<DateTime<Utc>>, // Recurrence dates (RDATE)
pub exdate: Vec<DateTime<Utc>>, // Exception dates (EXDATE)
pub recurrence_id: Option<DateTime<Utc>>, // Recurrence ID (RECURRENCE-ID)
// Alarms and attachments
pub alarms: Vec<VAlarm>, // VALARM components
pub attachments: Vec<Attachment>, // Attachments (ATTACH)
// CalDAV specific (for implementation)
pub etag: Option<String>, // ETag for CalDAV
pub href: Option<String>, // Href for CalDAV
pub calendar_path: Option<String>, // Calendar path
pub all_day: bool, // All-day event flag
}
// ==================== VTODO COMPONENT ====================
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct VTodo {
// Required properties
pub dtstamp: DateTime<Utc>, // Date-time stamp (DTSTAMP) - REQUIRED
pub uid: String, // Unique identifier (UID) - REQUIRED
// Optional date-time properties
pub dtstart: Option<DateTime<Utc>>, // Start date-time (DTSTART)
pub due: Option<DateTime<Utc>>, // Due date-time (DUE)
pub duration: Option<Duration>, // Duration (DURATION)
pub completed: Option<DateTime<Utc>>, // Completion date-time (COMPLETED)
// Descriptive properties
pub summary: Option<String>, // Summary/title (SUMMARY)
pub description: Option<String>, // Description (DESCRIPTION)
pub location: Option<String>, // Location (LOCATION)
// Status and completion
pub status: Option<TodoStatus>, // Status (STATUS)
pub percent_complete: Option<u8>, // Percent complete 0-100 (PERCENT-COMPLETE)
pub priority: Option<u8>, // Priority 0-9 (PRIORITY)
// People and organization
pub organizer: Option<CalendarUser>, // Organizer (ORGANIZER)
pub attendees: Vec<Attendee>, // Attendees (ATTENDEE)
pub contact: Option<String>, // Contact information (CONTACT)
// Categorization and relationships
pub categories: Vec<String>, // Categories (CATEGORIES)
pub comment: Option<String>, // Comment (COMMENT)
pub resources: Vec<String>, // Resources (RESOURCES)
pub related_to: Option<String>, // Related component (RELATED-TO)
pub url: Option<String>, // URL (URL)
// Geographical
pub geo: Option<GeographicPosition>, // Geographic position (GEO)
// Versioning and modification
pub sequence: Option<u32>, // Sequence number (SEQUENCE)
pub created: Option<DateTime<Utc>>, // Creation time (CREATED)
pub last_modified: Option<DateTime<Utc>>, // Last modified (LAST-MODIFIED)
// Recurrence
pub rrule: Option<String>, // Recurrence rule (RRULE)
pub rdate: Vec<DateTime<Utc>>, // Recurrence dates (RDATE)
pub exdate: Vec<DateTime<Utc>>, // Exception dates (EXDATE)
pub recurrence_id: Option<DateTime<Utc>>, // Recurrence ID (RECURRENCE-ID)
// Alarms and attachments
pub alarms: Vec<VAlarm>, // VALARM components
pub attachments: Vec<Attachment>, // Attachments (ATTACH)
}
// ==================== VJOURNAL COMPONENT ====================
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct VJournal {
// Required properties
pub dtstamp: DateTime<Utc>, // Date-time stamp (DTSTAMP) - REQUIRED
pub uid: String, // Unique identifier (UID) - REQUIRED
// Optional properties
pub dtstart: Option<DateTime<Utc>>, // Start date-time (DTSTART)
pub summary: Option<String>, // Summary/title (SUMMARY)
pub description: Option<String>, // Description (DESCRIPTION)
// Classification and status
pub class: Option<EventClass>, // Classification (CLASS)
pub status: Option<JournalStatus>, // Status (STATUS)
// People and organization
pub organizer: Option<CalendarUser>, // Organizer (ORGANIZER)
pub contact: Option<String>, // Contact information (CONTACT)
// Categorization and relationships
pub categories: Vec<String>, // Categories (CATEGORIES)
pub comment: Option<String>, // Comment (COMMENT)
pub related_to: Option<String>, // Related component (RELATED-TO)
pub url: Option<String>, // URL (URL)
// Versioning and modification
pub sequence: Option<u32>, // Sequence number (SEQUENCE)
pub created: Option<DateTime<Utc>>, // Creation time (CREATED)
pub last_modified: Option<DateTime<Utc>>, // Last modified (LAST-MODIFIED)
// Recurrence
pub rrule: Option<String>, // Recurrence rule (RRULE)
pub rdate: Vec<DateTime<Utc>>, // Recurrence dates (RDATE)
pub exdate: Vec<DateTime<Utc>>, // Exception dates (EXDATE)
pub recurrence_id: Option<DateTime<Utc>>, // Recurrence ID (RECURRENCE-ID)
// Attachments
pub attachments: Vec<Attachment>, // Attachments (ATTACH)
}
// ==================== VFREEBUSY COMPONENT ====================
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct VFreeBusy {
// Required properties
pub dtstamp: DateTime<Utc>, // Date-time stamp (DTSTAMP) - REQUIRED
pub uid: String, // Unique identifier (UID) - REQUIRED
// Optional properties
pub dtstart: Option<DateTime<Utc>>, // Start date-time (DTSTART)
pub dtend: Option<DateTime<Utc>>, // End date-time (DTEND)
pub duration: Option<Duration>, // Duration (DURATION)
// Free/busy information
pub freebusy: Vec<FreeBusyTime>, // Free/busy periods (FREEBUSY)
// People and organization
pub organizer: Option<CalendarUser>, // Organizer (ORGANIZER)
pub attendees: Vec<CalendarUser>, // Attendees (ATTENDEE)
pub contact: Option<String>, // Contact information (CONTACT)
// Additional properties
pub comment: Option<String>, // Comment (COMMENT)
pub url: Option<String>, // URL (URL)
}
// ==================== VTIMEZONE COMPONENT ====================
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct VTimeZone {
// Required properties
pub tzid: String, // Time zone identifier (TZID) - REQUIRED
// Optional properties
pub tzname: Option<String>, // Time zone name (TZNAME)
pub tzurl: Option<String>, // Time zone URL (TZURL)
// Standard and daylight components
pub standard: Vec<TimeZoneComponent>, // Standard time components
pub daylight: Vec<TimeZoneComponent>, // Daylight time components
// Last modified
pub last_modified: Option<DateTime<Utc>>, // Last modified (LAST-MODIFIED)
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TimeZoneComponent {
pub dtstart: DateTime<Utc>, // Start date-time (DTSTART) - REQUIRED
pub tzoffsetfrom: String, // UTC offset from (TZOFFSETFROM) - REQUIRED
pub tzoffsetto: String, // UTC offset to (TZOFFSETTO) - REQUIRED
pub tzname: Option<String>, // Time zone name (TZNAME)
pub comment: Option<String>, // Comment (COMMENT)
// Recurrence
pub rrule: Option<String>, // Recurrence rule (RRULE)
pub rdate: Vec<DateTime<Utc>>, // Recurrence dates (RDATE)
}
// ==================== VALARM COMPONENT ====================
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct VAlarm {
pub action: AlarmAction, // Action (ACTION) - REQUIRED
pub trigger: AlarmTrigger, // Trigger (TRIGGER) - REQUIRED
// Optional properties (some required based on action)
pub description: Option<String>, // Description (DESCRIPTION)
pub summary: Option<String>, // Summary (SUMMARY)
pub duration: Option<Duration>, // Duration (DURATION)
pub repeat: Option<u32>, // Repeat count (REPEAT)
pub attendees: Vec<CalendarUser>, // Attendees (ATTENDEE) - for EMAIL action
pub attachments: Vec<Attachment>, // Attachments (ATTACH)
}
// ==================== SUPPORTING TYPES ====================
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum EventClass {
Public,
Private,
Confidential,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum EventStatus {
Tentative,
Confirmed,
Cancelled,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TodoStatus {
NeedsAction,
Completed,
InProcess,
Cancelled,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum JournalStatus {
Draft,
Final,
Cancelled,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TimeTransparency {
Opaque, // Time is not available (default)
Transparent, // Time is available despite event
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AlarmAction {
Audio,
Display,
Email,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AlarmTrigger {
Duration(Duration), // Relative to start/end
DateTime(DateTime<Utc>), // Absolute time
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CalendarUser {
pub cal_address: String, // Calendar address (email)
pub cn: Option<String>, // Common name (CN parameter)
pub dir: Option<String>, // Directory entry (DIR parameter)
pub sent_by: Option<String>, // Sent by (SENT-BY parameter)
pub language: Option<String>, // Language (LANGUAGE parameter)
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Attendee {
pub cal_address: String, // Calendar address (email)
pub cn: Option<String>, // Common name (CN parameter)
pub role: Option<AttendeeRole>, // Role (ROLE parameter)
pub partstat: Option<ParticipationStatus>, // Participation status (PARTSTAT parameter)
pub rsvp: Option<bool>, // RSVP expectation (RSVP parameter)
pub cutype: Option<CalendarUserType>, // Calendar user type (CUTYPE parameter)
pub member: Vec<String>, // Group/list membership (MEMBER parameter)
pub delegated_to: Vec<String>, // Delegated to (DELEGATED-TO parameter)
pub delegated_from: Vec<String>, // Delegated from (DELEGATED-FROM parameter)
pub sent_by: Option<String>, // Sent by (SENT-BY parameter)
pub dir: Option<String>, // Directory entry (DIR parameter)
pub language: Option<String>, // Language (LANGUAGE parameter)
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AttendeeRole {
Chair,
ReqParticipant,
OptParticipant,
NonParticipant,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ParticipationStatus {
NeedsAction,
Accepted,
Declined,
Tentative,
Delegated,
Completed,
InProcess,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum CalendarUserType {
Individual,
Group,
Resource,
Room,
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GeographicPosition {
pub latitude: f64, // Latitude in decimal degrees
pub longitude: f64, // Longitude in decimal degrees
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Attachment {
pub data: AttachmentData, // Attachment data
pub fmttype: Option<String>, // Format type (FMTTYPE parameter)
pub encoding: Option<String>, // Encoding (ENCODING parameter)
pub filename: Option<String>, // Filename (X-FILENAME parameter)
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AttachmentData {
Uri(String), // URI reference
Binary(Vec<u8>), // Binary data
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FreeBusyTime {
pub period: (DateTime<Utc>, DateTime<Utc>), // Start and end time
pub fbtype: Option<FreeBusyType>, // Free/busy type
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum FreeBusyType {
Free,
Busy,
BusyUnavailable,
BusyTentative,
}
// ==================== COMPATIBILITY LAYER ====================
use crate::services::calendar_service::{CalendarEvent, EventReminder, ReminderAction};
// Conversion from new VEvent to existing CalendarEvent
impl From<VEvent> for CalendarEvent {
fn from(vevent: VEvent) -> Self {
Self {
uid: vevent.uid,
summary: vevent.summary,
description: vevent.description,
start: vevent.dtstart,
end: vevent.dtend,
location: vevent.location,
status: vevent.status.unwrap_or(EventStatus::Confirmed).into(),
class: vevent.class.unwrap_or(EventClass::Public).into(),
priority: vevent.priority,
organizer: vevent.organizer.map(|o| o.cal_address),
attendees: vevent.attendees.into_iter().map(|a| a.cal_address).collect(),
categories: vevent.categories,
created: vevent.created,
last_modified: vevent.last_modified,
recurrence_rule: vevent.rrule,
exception_dates: vevent.exdate,
all_day: vevent.all_day,
reminders: vevent.alarms.into_iter().map(|a| a.into()).collect(),
etag: vevent.etag,
href: vevent.href,
calendar_path: vevent.calendar_path,
}
}
}
// Conversion from existing CalendarEvent to new VEvent
impl From<CalendarEvent> for VEvent {
fn from(event: CalendarEvent) -> Self {
use chrono::Utc;
Self {
// Required properties
dtstamp: Utc::now(), // Add required DTSTAMP
uid: event.uid,
dtstart: event.start,
// Optional properties
dtend: event.end,
duration: None, // Will be calculated from dtend if needed
summary: event.summary,
description: event.description,
location: event.location,
// Classification and status
class: Some(event.class.into()),
status: Some(event.status.into()),
transp: None, // Default to None, can be enhanced later
priority: event.priority,
// People and organization
organizer: event.organizer.map(|email| CalendarUser {
cal_address: email,
cn: None,
dir: None,
sent_by: None,
language: None,
}),
attendees: event.attendees.into_iter().map(|email| Attendee {
cal_address: email,
cn: None,
role: None,
partstat: None,
rsvp: None,
cutype: None,
member: Vec::new(),
delegated_to: Vec::new(),
delegated_from: Vec::new(),
sent_by: None,
dir: None,
language: None,
}).collect(),
contact: None,
// Categorization and relationships
categories: event.categories,
comment: None,
resources: Vec::new(),
related_to: None,
url: None,
// Geographical
geo: None,
// Versioning and modification
sequence: Some(0), // Start with sequence 0
created: event.created,
last_modified: event.last_modified,
// Recurrence
rrule: event.recurrence_rule,
rdate: Vec::new(),
exdate: event.exception_dates,
recurrence_id: None,
// Alarms and attachments
alarms: event.reminders.into_iter().map(|r| r.into()).collect(),
attachments: Vec::new(),
// CalDAV specific
etag: event.etag,
href: event.href,
calendar_path: event.calendar_path,
all_day: event.all_day,
}
}
}
// Convert between status enums
impl From<EventStatus> for crate::services::calendar_service::EventStatus {
fn from(status: EventStatus) -> Self {
match status {
EventStatus::Tentative => crate::services::calendar_service::EventStatus::Tentative,
EventStatus::Confirmed => crate::services::calendar_service::EventStatus::Confirmed,
EventStatus::Cancelled => crate::services::calendar_service::EventStatus::Cancelled,
}
}
}
impl From<crate::services::calendar_service::EventStatus> for EventStatus {
fn from(status: crate::services::calendar_service::EventStatus) -> Self {
match status {
crate::services::calendar_service::EventStatus::Tentative => EventStatus::Tentative,
crate::services::calendar_service::EventStatus::Confirmed => EventStatus::Confirmed,
crate::services::calendar_service::EventStatus::Cancelled => EventStatus::Cancelled,
}
}
}
// Convert between class enums
impl From<EventClass> for crate::services::calendar_service::EventClass {
fn from(class: EventClass) -> Self {
match class {
EventClass::Public => crate::services::calendar_service::EventClass::Public,
EventClass::Private => crate::services::calendar_service::EventClass::Private,
EventClass::Confidential => crate::services::calendar_service::EventClass::Confidential,
}
}
}
impl From<crate::services::calendar_service::EventClass> for EventClass {
fn from(class: crate::services::calendar_service::EventClass) -> Self {
match class {
crate::services::calendar_service::EventClass::Public => EventClass::Public,
crate::services::calendar_service::EventClass::Private => EventClass::Private,
crate::services::calendar_service::EventClass::Confidential => EventClass::Confidential,
}
}
}
// Convert between reminder types
impl From<VAlarm> for EventReminder {
fn from(alarm: VAlarm) -> Self {
let minutes_before = match alarm.trigger {
AlarmTrigger::Duration(duration) => {
// Convert duration to minutes (assuming it's negative for "before")
(-duration.num_minutes()) as i32
},
AlarmTrigger::DateTime(_) => 0, // Absolute time alarms default to 0 minutes
};
let action = match alarm.action {
AlarmAction::Display => ReminderAction::Display,
AlarmAction::Audio => ReminderAction::Audio,
AlarmAction::Email => ReminderAction::Email,
};
Self {
minutes_before,
action,
description: alarm.description,
}
}
}
impl From<EventReminder> for VAlarm {
fn from(reminder: EventReminder) -> Self {
use chrono::Duration;
let action = match reminder.action {
ReminderAction::Display => AlarmAction::Display,
ReminderAction::Audio => AlarmAction::Audio,
ReminderAction::Email => AlarmAction::Email,
};
let trigger = AlarmTrigger::Duration(Duration::minutes(-reminder.minutes_before as i64));
Self {
action,
trigger,
description: reminder.description,
summary: None,
duration: None,
repeat: None,
attendees: Vec::new(),
attachments: Vec::new(),
}
}
}
// ==================== CONVERSION FUNCTIONS ====================
impl VEvent {
/// Convert from legacy CalendarEvent to RFC 5545 compliant VEvent
pub fn from_calendar_event(event: &crate::services::calendar_service::CalendarEvent) -> Self {
let now = chrono::Utc::now();
// Convert attendees from strings to structured Attendee objects
let attendees = event.attendees.iter()
.map(|email| Attendee {
cal_address: email.clone(),
cn: None,
role: Some(AttendeeRole::ReqParticipant),
partstat: Some(ParticipationStatus::NeedsAction),
rsvp: Some(true),
cutype: Some(CalendarUserType::Individual),
member: Vec::new(),
delegated_to: Vec::new(),
delegated_from: Vec::new(),
sent_by: None,
dir: None,
language: None,
})
.collect();
// Convert reminders to alarms
let alarms = event.reminders.iter()
.map(|reminder| {
let action = match reminder.action {
crate::services::calendar_service::ReminderAction::Display => AlarmAction::Display,
crate::services::calendar_service::ReminderAction::Audio => AlarmAction::Audio,
crate::services::calendar_service::ReminderAction::Email => AlarmAction::Email,
};
VAlarm {
action,
trigger: AlarmTrigger::Duration(chrono::Duration::minutes(-reminder.minutes_before as i64)),
description: reminder.description.clone(),
summary: None,
duration: None,
repeat: None,
attendees: Vec::new(),
attachments: Vec::new(),
}
})
.collect();
// Convert status
let status = match event.status {
crate::services::calendar_service::EventStatus::Tentative => Some(EventStatus::Tentative),
crate::services::calendar_service::EventStatus::Confirmed => Some(EventStatus::Confirmed),
crate::services::calendar_service::EventStatus::Cancelled => Some(EventStatus::Cancelled),
};
// Convert class
let class = match event.class {
crate::services::calendar_service::EventClass::Public => Some(EventClass::Public),
crate::services::calendar_service::EventClass::Private => Some(EventClass::Private),
crate::services::calendar_service::EventClass::Confidential => Some(EventClass::Confidential),
};
Self {
// Required properties
dtstamp: event.last_modified.unwrap_or(now),
uid: event.uid.clone(),
dtstart: event.start,
// Date/time properties
dtend: event.end,
duration: None, // Will use dtend instead
// Content properties
summary: event.summary.clone(),
description: event.description.clone(),
location: event.location.clone(),
// Classification and status
class,
status,
transp: None, // Default transparency
priority: event.priority,
// People and organization
organizer: event.organizer.as_ref().map(|org| CalendarUser {
cal_address: org.clone(),
cn: None,
dir: None,
sent_by: None,
language: None,
}),
attendees,
contact: None,
// Categorization
categories: event.categories.clone(),
comment: None,
resources: Vec::new(),
related_to: None,
url: None,
// Geographic
geo: None,
// Versioning
sequence: Some(0), // Start with sequence 0
created: event.created,
last_modified: event.last_modified,
// Recurrence
rrule: event.recurrence_rule.clone(),
rdate: Vec::new(),
exdate: event.exception_dates.clone(),
recurrence_id: None,
// Alarms and attachments
alarms,
attachments: Vec::new(),
// CalDAV specific
etag: event.etag.clone(),
href: event.href.clone(),
calendar_path: event.calendar_path.clone(),
all_day: event.all_day,
}
}
/// Convert to legacy CalendarEvent for backward compatibility
#[allow(dead_code)]
pub fn to_calendar_event(&self) -> crate::services::calendar_service::CalendarEvent {
// Convert attendees back to simple email strings
let attendees = self.attendees.iter()
.map(|attendee| attendee.cal_address.clone())
.collect();
// Convert alarms back to simple reminders
let reminders = self.alarms.iter()
.filter_map(|alarm| {
if let AlarmTrigger::Duration(duration) = &alarm.trigger {
Some(crate::services::calendar_service::EventReminder {
minutes_before: (-duration.num_minutes()) as i32,
action: match alarm.action {
AlarmAction::Display => crate::services::calendar_service::ReminderAction::Display,
AlarmAction::Audio => crate::services::calendar_service::ReminderAction::Audio,
AlarmAction::Email => crate::services::calendar_service::ReminderAction::Email,
},
description: alarm.description.clone(),
})
} else {
None
}
})
.collect();
// Convert status
let status = match self.status {
Some(EventStatus::Tentative) => crate::services::calendar_service::EventStatus::Tentative,
Some(EventStatus::Cancelled) => crate::services::calendar_service::EventStatus::Cancelled,
_ => crate::services::calendar_service::EventStatus::Confirmed,
};
// Convert class
let class = match self.class {
Some(EventClass::Private) => crate::services::calendar_service::EventClass::Private,
Some(EventClass::Confidential) => crate::services::calendar_service::EventClass::Confidential,
_ => crate::services::calendar_service::EventClass::Public,
};
crate::services::calendar_service::CalendarEvent {
uid: self.uid.clone(),
summary: self.summary.clone(),
description: self.description.clone(),
start: self.dtstart,
end: self.dtend,
location: self.location.clone(),
status,
class,
priority: self.priority,
organizer: self.organizer.as_ref().map(|org| org.cal_address.clone()),
attendees,
categories: self.categories.clone(),
created: self.created,
last_modified: self.last_modified,
recurrence_rule: self.rrule.clone(),
exception_dates: self.exdate.clone(),
all_day: self.all_day,
reminders,
etag: self.etag.clone(),
href: self.href.clone(),
calendar_path: self.calendar_path.clone(),
}
}
/// Get the date for this event (for calendar display)
pub fn get_date(&self) -> chrono::NaiveDate {
if self.all_day {
self.dtstart.date_naive()
} else {
self.dtstart.date_naive()
}
}
/// Get display title for the event
pub fn get_title(&self) -> String {
self.summary.clone().unwrap_or_else(|| "Untitled Event".to_string())
}
/// Get display string for status
pub fn get_status_display(&self) -> &'static str {
match self.status {
Some(EventStatus::Tentative) => "Tentative",
Some(EventStatus::Confirmed) => "Confirmed",
Some(EventStatus::Cancelled) => "Cancelled",
None => "Confirmed", // Default
}
}
/// Get display string for class
pub fn get_class_display(&self) -> &'static str {
match self.class {
Some(EventClass::Public) => "Public",
Some(EventClass::Private) => "Private",
Some(EventClass::Confidential) => "Confidential",
None => "Public", // Default
}
}
/// Get display string for priority
pub fn get_priority_display(&self) -> String {
match self.priority {
None => "Not set".to_string(),
Some(0) => "Undefined".to_string(),
Some(1) => "High".to_string(),
Some(p) if p <= 4 => "High".to_string(),
Some(5) => "Medium".to_string(),
Some(p) if p <= 8 => "Low".to_string(),
Some(9) => "Low".to_string(),
Some(p) => format!("Priority {}", p),
}
}
}
// Re-export from shared calendar-models library for backward compatibility
pub use calendar_models::*;

View File

@@ -5,10 +5,11 @@ use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response};
use std::collections::HashMap;
// Import RFC 5545 compliant VEvent
use crate::models::ical::VEvent;
// Import RFC 5545 compliant VEvent from shared library
use calendar_models::VEvent;
// Create type alias for smooth transition
// Create type alias for backward compatibility
pub type CalendarEvent = VEvent;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EventReminder {
@@ -44,56 +45,7 @@ pub struct CalendarInfo {
pub color: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CalendarEvent {
pub uid: String,
pub summary: Option<String>,
pub description: Option<String>,
pub start: DateTime<Utc>,
pub end: Option<DateTime<Utc>>,
pub location: Option<String>,
pub status: EventStatus,
pub class: EventClass,
pub priority: Option<u8>,
pub organizer: Option<String>,
pub attendees: Vec<String>,
pub categories: Vec<String>,
pub created: Option<DateTime<Utc>>,
pub last_modified: Option<DateTime<Utc>>,
pub recurrence_rule: Option<String>,
pub exception_dates: Vec<DateTime<Utc>>,
pub all_day: bool,
pub reminders: Vec<EventReminder>,
pub etag: Option<String>,
pub href: Option<String>,
pub calendar_path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum EventStatus {
Tentative,
Confirmed,
Cancelled,
}
impl Default for EventStatus {
fn default() -> Self {
EventStatus::Confirmed
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum EventClass {
Public,
Private,
Confidential,
}
impl Default for EventClass {
fn default() -> Self {
EventClass::Public
}
}
// CalendarEvent, EventStatus, and EventClass are now imported from shared library
// ==================== V2 API MODELS ====================
@@ -228,15 +180,8 @@ impl CalendarService {
year: i32,
month: u32
) -> Result<Vec<VEvent>, String> {
// Fetch CalendarEvents first
let calendar_events = self.fetch_events_for_month(token, password, year, month).await?;
// Convert to VEvent
let vevents = calendar_events.into_iter()
.map(|event| VEvent::from_calendar_event(&event))
.collect();
Ok(vevents)
// Since CalendarEvent is now a type alias for VEvent, just call the main method
self.fetch_events_for_month(token, password, year, month).await
}
pub async fn fetch_events_for_month(