Fix frontend to use series endpoint for single occurrence modifications

- Extended update callback signature to include update_scope and occurrence_date parameters
- Modified app.rs to detect when series endpoint should be used vs regular endpoint
- Updated calendar_service to automatically set occurrence_date for "this_only" updates
- Modified all callback emit calls throughout frontend to include new parameters
- Week_view now properly calls series endpoint with "this_only" scope for single occurrence edits

This ensures that single occurrence modifications (RecurringEditAction::ThisEvent)
now go through the proper series endpoint which will:
- Add EXDATE to the original recurring series
- Create exception event with RECURRENCE-ID
- Show proper debug logging in backend

🤖 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 14:01:30 -04:00
parent 071fc3099f
commit a6aac42c78
3 changed files with 96 additions and 39 deletions

View File

@@ -353,17 +353,23 @@ pub async fn update_event_series(
},
"this_only" => {
// Create exception for single occurrence, keep original series
update_single_occurrence(&mut existing_event, &request, start_datetime, end_datetime)?
let event_href = existing_event.href.as_ref()
.ok_or_else(|| ApiError::Internal("Event missing href for single occurrence update".to_string()))?
.clone();
update_single_occurrence(&mut existing_event, &request, start_datetime, end_datetime, &client, &calendar_path, &event_href).await?
},
_ => unreachable!(), // Already validated above
};
// Update the event on the CalDAV server using the original event's href
let event_href = existing_event.href.as_ref()
.ok_or_else(|| ApiError::Internal("Event missing href for update".to_string()))?;
client.update_event(&calendar_path, &updated_event, event_href)
.await
.map_err(|e| ApiError::Internal(format!("Failed to update event series: {}", e)))?;
// Note: For "this_only" updates, the original series was already updated in update_single_occurrence
if request.update_scope != "this_only" {
let event_href = existing_event.href.as_ref()
.ok_or_else(|| ApiError::Internal("Event missing href for update".to_string()))?;
client.update_event(&calendar_path, &updated_event, event_href)
.await
.map_err(|e| ApiError::Internal(format!("Failed to update event series: {}", e)))?;
}
println!("✅ Event series updated successfully with UID: {}", request.series_uid);
@@ -636,24 +642,40 @@ fn update_this_and_future(
}
/// Update only a single occurrence (create an exception)
fn update_single_occurrence(
async fn update_single_occurrence(
existing_event: &mut VEvent,
request: &UpdateEventSeriesRequest,
start_datetime: chrono::DateTime<chrono::Utc>,
end_datetime: chrono::DateTime<chrono::Utc>,
client: &CalDAVClient,
calendar_path: &str,
original_event_href: &str,
) -> Result<(VEvent, u32), ApiError> {
// For single occurrence updates, we need to:
// 1. Keep the original series unchanged
// 2. Create a new single event (exception) with the same UID but different RECURRENCE-ID
// For RFC 5545 compliant single occurrence updates, we need to:
// 1. Add EXDATE to the original series to exclude this occurrence
// 2. Create a new exception event with RECURRENCE-ID pointing to the original occurrence
// Create a new event for the single occurrence
let occurrence_uid = if let Some(occurrence_date) = &request.occurrence_date {
format!("{}-exception-{}", existing_event.uid, occurrence_date)
} else {
format!("{}-exception", existing_event.uid)
};
// First, add EXDATE to the original series
let occurrence_date = request.occurrence_date.as_ref()
.ok_or_else(|| ApiError::BadRequest("occurrence_date is required for single occurrence updates".to_string()))?;
let mut exception_event = VEvent::new(occurrence_uid, start_datetime);
// Parse the occurrence date
let exception_date = chrono::NaiveDate::parse_from_str(occurrence_date, "%Y-%m-%d")
.map_err(|_| ApiError::BadRequest("Invalid occurrence date format. Expected YYYY-MM-DD".to_string()))?;
// Create the EXDATE datetime using the original event's time
let original_time = existing_event.dtstart.time();
let exception_datetime = exception_date.and_time(original_time);
let exception_utc = chrono::Utc.from_utc_datetime(&exception_datetime);
// Add the exception date to the original series
println!("📝 BEFORE adding EXDATE: existing_event.exdate = {:?}", existing_event.exdate);
existing_event.exdate.push(exception_utc);
println!("📝 AFTER adding EXDATE: existing_event.exdate = {:?}", existing_event.exdate);
println!("🚫 Added EXDATE for single occurrence modification: {}", exception_utc.format("%Y-%m-%d %H:%M:%S"));
// Create a new exception event with the same UID but RECURRENCE-ID
let mut exception_event = VEvent::new(existing_event.uid.clone(), start_datetime);
exception_event.dtend = Some(end_datetime);
exception_event.summary = if request.title.trim().is_empty() { None } else { Some(request.title.clone()) };
exception_event.description = if request.description.trim().is_empty() { None } else { Some(request.description.clone()) };
@@ -672,10 +694,41 @@ fn update_single_occurrence(
});
exception_event.priority = request.priority;
// Note: This function returns the exception event, but in a full implementation,
// we would need to create this as a separate event and add an EXDATE to the original series
Ok((exception_event, 1)) // 1 occurrence updated
// Set RECURRENCE-ID to point to the original occurrence
exception_event.recurrence_id = Some(exception_utc);
// Remove any recurrence rules from the exception (it's a single event)
exception_event.rrule = None;
exception_event.rdate.clear();
exception_event.exdate.clear();
// Set calendar path for the exception event
exception_event.calendar_path = Some(calendar_path.to_string());
println!("✨ Created exception event with RECURRENCE-ID: {}", exception_utc.format("%Y-%m-%d %H:%M:%S"));
// First, update the original series with the EXDATE
println!("📤 About to update CalDAV server with event containing {} EXDATE entries", existing_event.exdate.len());
for (i, exdate) in existing_event.exdate.iter().enumerate() {
println!("📤 EXDATE[{}]: {}", i, exdate.format("%Y-%m-%d %H:%M:%S UTC"));
}
client.update_event(calendar_path, existing_event, original_event_href)
.await
.map_err(|e| ApiError::Internal(format!("Failed to update original series with EXDATE: {}", e)))?;
println!("✅ Updated original series with EXDATE");
// Then create the exception event as a new event
let exception_href = client.create_event(calendar_path, &exception_event)
.await
.map_err(|e| ApiError::Internal(format!("Failed to create exception event: {}", e)))?;
println!("✅ Created exception event with href: {}", exception_href);
// Return the original series (now with EXDATE) as the "updated" event
Ok((existing_event.clone(), 1)) // 1 occurrence modified (via exception)
}
/// Delete the entire series