diff --git a/backend/src/handlers/series.rs b/backend/src/handlers/series.rs index 39c508c..23663a8 100644 --- a/backend/src/handlers/series.rs +++ b/backend/src/handlers/series.rs @@ -559,56 +559,56 @@ fn update_entire_series( start_datetime: chrono::DateTime, end_datetime: chrono::DateTime, ) -> Result<(VEvent, u32), ApiError> { - // Create a new series request for RRULE generation - let series_request = CreateEventSeriesRequest { - title: request.title.clone(), - description: request.description.clone(), - start_date: request.start_date.clone(), - start_time: request.start_time.clone(), - end_date: request.end_date.clone(), - end_time: request.end_time.clone(), - location: request.location.clone(), - all_day: request.all_day, - status: request.status.clone(), - class: request.class.clone(), - priority: request.priority, - organizer: request.organizer.clone(), - attendees: request.attendees.clone(), - categories: request.categories.clone(), - reminder: request.reminder.clone(), - recurrence: request.recurrence.clone(), - recurrence_days: request.recurrence_days.clone(), - recurrence_interval: request.recurrence_interval, - recurrence_end_date: request.recurrence_end_date.clone(), - recurrence_count: request.recurrence_count, - calendar_path: None, // Not needed for RRULE generation - }; - - // Update all fields of the existing event - existing_event.dtstart = start_datetime; - existing_event.dtend = Some(end_datetime); - existing_event.summary = if request.title.trim().is_empty() { None } else { Some(request.title.clone()) }; - existing_event.description = if request.description.trim().is_empty() { None } else { Some(request.description.clone()) }; - existing_event.location = if request.location.trim().is_empty() { None } else { Some(request.location.clone()) }; + // Clone the existing event to preserve all metadata + let mut updated_event = existing_event.clone(); - existing_event.status = Some(match request.status.to_lowercase().as_str() { + // Update only the modified properties from the request + updated_event.dtstart = start_datetime; + updated_event.dtend = Some(end_datetime); + updated_event.summary = if request.title.trim().is_empty() { + existing_event.summary.clone() // Keep original if empty + } else { + Some(request.title.clone()) + }; + updated_event.description = if request.description.trim().is_empty() { + existing_event.description.clone() // Keep original if empty + } else { + Some(request.description.clone()) + }; + updated_event.location = if request.location.trim().is_empty() { + existing_event.location.clone() // Keep original if empty + } else { + Some(request.location.clone()) + }; + + updated_event.status = Some(match request.status.to_lowercase().as_str() { "tentative" => EventStatus::Tentative, "cancelled" => EventStatus::Cancelled, _ => EventStatus::Confirmed, }); - existing_event.class = Some(match request.class.to_lowercase().as_str() { + updated_event.class = Some(match request.class.to_lowercase().as_str() { "private" => EventClass::Private, "confidential" => EventClass::Confidential, _ => EventClass::Public, }); - existing_event.priority = request.priority; + updated_event.priority = request.priority; - // Update the RRULE - existing_event.rrule = Some(build_series_rrule(&series_request)?); + // Update timestamps + let now = chrono::Utc::now(); + updated_event.dtstamp = now; + updated_event.last_modified = Some(now); + // Keep original created timestamp to preserve event history - Ok((existing_event.clone(), 1)) // 1 series updated (affects all occurrences) + // For simple updates (like drag operations), preserve the existing RRULE + // For more complex updates, we might need to regenerate it, but for now keep it simple + // updated_event.rrule remains unchanged from the clone + + // Copy the updated event back to existing_event for the main handler + *existing_event = updated_event.clone(); + + Ok((updated_event, 1)) // 1 series updated (affects all occurrences) } /// Update this occurrence and all future occurrences (RFC 5545 compliant series splitting) @@ -665,6 +665,9 @@ async fn update_this_and_future( calendar_path: &str, ) -> Result<(VEvent, u32), ApiError> { + // Clone the existing event to create the new series before modifying the RRULE of the + // original, because we'd like to preserve the original UNTIL logic + let mut new_series = existing_event.clone(); let occurrence_date = request.occurrence_date.as_ref() .ok_or_else(|| ApiError::BadRequest("occurrence_date is required for this_and_future updates".to_string()))?; @@ -688,7 +691,6 @@ async fn update_this_and_future( // Step 2: Create a new series starting from the occurrence date with updated properties let new_series_uid = format!("series-{}", uuid::Uuid::new_v4()); - let mut new_series = existing_event.clone(); // Update the new series with new properties new_series.uid = new_series_uid.clone(); @@ -712,12 +714,6 @@ async fn update_this_and_future( new_series.priority = request.priority; - // Reset the RRULE for the new series (remove UNTIL) - let new_rrule_parts: Vec<&str> = original_rrule.split(';').filter(|part| { - !part.starts_with("UNTIL=") && !part.starts_with("COUNT=") - }).collect(); - new_series.rrule = Some(new_rrule_parts.join(";")); - // Update timestamps let now = chrono::Utc::now(); new_series.dtstamp = now;