diff --git a/crates/db-user/src/events_ops.rs b/crates/db-user/src/events_ops.rs index 90e52d5a59..d451e142c4 100644 --- a/crates/db-user/src/events_ops.rs +++ b/crates/db-user/src/events_ops.rs @@ -40,7 +40,8 @@ impl UserDatabase { start_date = :start_date, end_date = :end_date, google_event_url = :google_event_url, - participants = :participants + participants = :participants, + is_recurring = :is_recurring WHERE id = :id RETURNING *", libsql::named_params! { @@ -53,6 +54,7 @@ impl UserDatabase { ":end_date": event.end_date.to_rfc3339(), ":google_event_url": event.google_event_url, ":participants": event.participants, + ":is_recurring": event.is_recurring, }, ) .await?; @@ -84,7 +86,8 @@ impl UserDatabase { start_date, end_date, google_event_url, - participants + participants, + is_recurring ) VALUES ( :id, :user_id, @@ -95,14 +98,16 @@ impl UserDatabase { :start_date, :end_date, :google_event_url, - :participants + :participants, + :is_recurring ) ON CONFLICT(tracking_id) DO UPDATE SET name = :name, note = :note, start_date = :start_date, end_date = :end_date, google_event_url = :google_event_url, - participants = :participants + participants = :participants, + is_recurring = :is_recurring RETURNING *", libsql::named_params! { ":id": event.id, @@ -115,6 +120,7 @@ impl UserDatabase { ":end_date": event.end_date.to_rfc3339(), ":google_event_url": event.google_event_url, ":participants": event.participants, + ":is_recurring": event.is_recurring, }, ) .await?; @@ -241,6 +247,7 @@ mod tests { end_date: chrono::Utc::now(), google_event_url: None, participants: None, + is_recurring: false, }; let event = db.upsert_event(event).await.unwrap(); diff --git a/plugins/apple-calendar/src/sync.rs b/plugins/apple-calendar/src/sync.rs index 56785d5abb..679ae38c28 100644 --- a/plugins/apple-calendar/src/sync.rs +++ b/plugins/apple-calendar/src/sync.rs @@ -269,6 +269,48 @@ async fn _sync_events( continue; } + // Check for backward compatibility: recurring event replacing old non-recurring + if system_event.is_recurring { + // Look for old format event with session + if let Some((_, session)) = + db_events_with_session.iter().find(|(db_event, session)| { + db_event.tracking_id == system_event.id && // Old format used base ID only + db_event.start_date == system_event.start_date && + db_event.name == system_event.name && + !db_event.is_recurring && // Was stored as non-recurring + session.is_some() // Has a session + }) + { + // Store session ID for transfer after new event is created + if let Some(session) = session { + let new_event_id = uuid::Uuid::new_v4().to_string(); + state + .session_transfers + .push((session.id.clone(), new_event_id.clone())); + + let new_event = hypr_db_user::Event { + id: new_event_id, + tracking_id: composite_tracking_id, + user_id: user_id.clone(), + calendar_id: Some(db_calendar.id.clone()), + name: system_event.name.clone(), + note: system_event.note.clone(), + start_date: system_event.start_date, + end_date: system_event.end_date, + google_event_url: None, + participants: Some( + serde_json::to_string(&system_event.participants) + .unwrap_or_else(|_| "[]".to_string()), + ), + is_recurring: system_event.is_recurring, + }; + + state.to_upsert.push(new_event); + continue; + } + } + } + // This is a genuinely new event let new_event = hypr_db_user::Event { id: uuid::Uuid::new_v4().to_string(), @@ -286,6 +328,7 @@ async fn _sync_events( ), is_recurring: system_event.is_recurring, }; + state.to_upsert.push(new_event); } } @@ -498,6 +541,7 @@ struct EventSyncState { to_delete: Vec, to_upsert: Vec, to_update: Vec, + session_transfers: Vec<(String, String)>, // (session_id, new_event_id) } impl CalendarSyncState { @@ -518,21 +562,31 @@ impl CalendarSyncState { impl EventSyncState { async fn execute(self, db: &hypr_db_user::UserDatabase) { - for event in self.to_delete { - if let Err(e) = db.delete_event(&event.id).await { - tracing::error!("delete_event_error: {}", e); + // 1. Create new events first + for event in self.to_upsert { + if let Err(e) = db.upsert_event(event).await { + tracing::error!("upsert_event_error: {}", e); } } + // 2. Update existing events for event in self.to_update { if let Err(e) = db.update_event(event).await { tracing::error!("update_event_error: {}", e); } } - for event in self.to_upsert { - if let Err(e) = db.upsert_event(event).await { - tracing::error!("upsert_event_error: {}", e); + // 3. Transfer sessions from old events to new events + for (session_id, new_event_id) in self.session_transfers { + if let Err(e) = db.session_set_event(session_id, Some(new_event_id)).await { + tracing::error!("session_transfer_error: {}", e); + } + } + + // 4. Delete old events last (after sessions have been transferred) + for event in self.to_delete { + if let Err(e) = db.delete_event(&event.id).await { + tracing::error!("delete_event_error: {}", e); } } }