// Simplified RFC 5545-based API models // Axum imports removed - not needed for model definitions use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; // ==================== CALENDAR REQUESTS ==================== #[derive(Debug, Deserialize)] pub struct CreateCalendarRequestV2 { pub name: String, pub description: Option, pub color: Option, } #[derive(Debug, Deserialize)] pub struct DeleteCalendarRequestV2 { pub path: String, } // ==================== EVENT REQUESTS ==================== // Simplified create event request using proper DateTime instead of string parsing #[derive(Debug, Deserialize)] pub struct CreateEventRequestV2 { pub summary: String, // title -> summary (RFC 5545 term) pub description: Option, // Optional in RFC 5545 pub dtstart: DateTime, // Direct DateTime, no string parsing! pub dtend: Option>, // Optional, alternative to duration pub location: Option, pub all_day: bool, // Status and classification pub status: Option, // Use enum instead of string pub class: Option, // Use enum instead of string pub priority: Option, // 0-9 priority level // People pub organizer: Option, // Organizer email pub attendees: Vec, // Rich attendee objects // Categorization pub categories: Vec, // Direct Vec instead of comma-separated // Recurrence (simplified for now) pub rrule: Option, // Standard RRULE format // Reminders (simplified for now) pub alarms: Vec, // Structured alarms // Calendar context pub calendar_path: Option, } #[derive(Debug, Deserialize)] pub struct UpdateEventRequestV2 { pub uid: String, // Event UID to identify which event to update pub summary: String, pub description: Option, pub dtstart: DateTime, // Direct DateTime, no string parsing! pub dtend: Option>, pub location: Option, pub all_day: bool, // Status and classification pub status: Option, pub class: Option, pub priority: Option, // People pub organizer: Option, pub attendees: Vec, // Categorization pub categories: Vec, // Recurrence pub rrule: Option, // Reminders pub alarms: Vec, // Context pub calendar_path: Option, pub update_action: Option, // "update_series" for recurring events pub occurrence_date: Option>, // Specific occurrence pub exception_dates: Option>>, // EXDATE pub until_date: Option>, // RRULE UNTIL clause } #[derive(Debug, Deserialize)] pub struct DeleteEventRequestV2 { pub calendar_path: String, pub event_href: String, pub delete_action: DeleteActionV2, // Use enum instead of string pub occurrence_date: Option>, // Direct DateTime } // ==================== SUPPORTING TYPES ==================== #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum EventStatusV2 { Tentative, Confirmed, Cancelled, } impl Default for EventStatusV2 { fn default() -> Self { EventStatusV2::Confirmed } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum EventClassV2 { Public, Private, Confidential, } impl Default for EventClassV2 { fn default() -> Self { EventClassV2::Public } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum DeleteActionV2 { DeleteThis, // "delete_this" DeleteFollowing, // "delete_following" DeleteSeries, // "delete_series" } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct AttendeeV2 { pub email: String, // Calendar address pub name: Option, // Common name (CN parameter) pub role: Option, // Role (ROLE parameter) pub status: Option, // Participation status (PARTSTAT parameter) pub rsvp: Option, // RSVP expectation (RSVP parameter) } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum AttendeeRoleV2 { Chair, Required, // REQ-PARTICIPANT Optional, // OPT-PARTICIPANT NonParticipant, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum ParticipationStatusV2 { NeedsAction, Accepted, Declined, Tentative, Delegated, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct AlarmV2 { pub action: AlarmActionV2, // Action (AUDIO, DISPLAY, EMAIL) pub trigger_minutes: i32, // Minutes before event (negative = before) pub description: Option, // Description for display/email } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum AlarmActionV2 { Audio, Display, Email, } // ==================== RESPONSES ==================== #[derive(Debug, Serialize)] pub struct CreateEventResponseV2 { pub success: bool, pub message: String, pub event: Option, // Return created event summary } #[derive(Debug, Serialize)] pub struct UpdateEventResponseV2 { pub success: bool, pub message: String, pub event: Option, // Return updated event summary } #[derive(Debug, Serialize)] pub struct DeleteEventResponseV2 { pub success: bool, pub message: String, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct EventSummaryV2 { pub uid: String, pub summary: Option, pub dtstart: DateTime, pub dtend: Option>, pub location: Option, pub all_day: bool, pub href: Option, pub etag: Option, } // ==================== CONVERSION HELPERS ==================== // Convert from old request format to new for backward compatibility impl From for CreateEventRequestV2 { fn from(old: crate::models::CreateEventRequest) -> Self { use chrono::{NaiveDate, NaiveTime, TimeZone, Utc}; // Parse the old string-based date/time format let start_date = NaiveDate::parse_from_str(&old.start_date, "%Y-%m-%d") .unwrap_or_else(|_| chrono::Utc::now().date_naive()); let start_time = NaiveTime::parse_from_str(&old.start_time, "%H:%M") .unwrap_or_else(|_| NaiveTime::from_hms_opt(0, 0, 0).unwrap()); let dtstart = Utc.from_utc_datetime(&start_date.and_time(start_time)); let end_date = NaiveDate::parse_from_str(&old.end_date, "%Y-%m-%d") .unwrap_or_else(|_| chrono::Utc::now().date_naive()); let end_time = NaiveTime::parse_from_str(&old.end_time, "%H:%M") .unwrap_or_else(|_| NaiveTime::from_hms_opt(1, 0, 0).unwrap()); let dtend = Some(Utc.from_utc_datetime(&end_date.and_time(end_time))); // Parse comma-separated categories let categories: Vec = if old.categories.trim().is_empty() { Vec::new() } else { old.categories.split(',').map(|s| s.trim().to_string()).collect() }; // Parse comma-separated attendees let attendees: Vec = if old.attendees.trim().is_empty() { Vec::new() } else { old.attendees.split(',').map(|email| AttendeeV2 { email: email.trim().to_string(), name: None, role: Some(AttendeeRoleV2::Required), status: Some(ParticipationStatusV2::NeedsAction), rsvp: Some(true), }).collect() }; // Convert status string to enum let status = match old.status.as_str() { "tentative" => Some(EventStatusV2::Tentative), "confirmed" => Some(EventStatusV2::Confirmed), "cancelled" => Some(EventStatusV2::Cancelled), _ => Some(EventStatusV2::Confirmed), }; // Convert class string to enum let class = match old.class.as_str() { "public" => Some(EventClassV2::Public), "private" => Some(EventClassV2::Private), "confidential" => Some(EventClassV2::Confidential), _ => Some(EventClassV2::Public), }; // Create basic alarm if reminder specified let alarms = if old.reminder == "none" { Vec::new() } else { // Default to 15 minutes before for now vec![AlarmV2 { action: AlarmActionV2::Display, trigger_minutes: 15, description: Some("Event reminder".to_string()), }] }; Self { summary: old.title, description: if old.description.trim().is_empty() { None } else { Some(old.description) }, dtstart, dtend, location: if old.location.trim().is_empty() { None } else { Some(old.location) }, all_day: old.all_day, status, class, priority: old.priority, organizer: if old.organizer.trim().is_empty() { None } else { Some(old.organizer) }, attendees, categories, rrule: None, // TODO: Convert recurrence string to RRULE alarms, calendar_path: old.calendar_path, } } } // Error handling - ApiError is available through crate::models::ApiError in handlers