diff --git a/bitbylaw/delete_all_calendars.py b/bitbylaw/delete_all_calendars.py new file mode 100644 index 00000000..afd4edcf --- /dev/null +++ b/bitbylaw/delete_all_calendars.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +""" +Script to clear all events from all calendars in Google Calendar account +""" +import asyncio +import sys +import os +sys.path.append('.') + +from steps.advoware_cal_sync.calendar_sync_event_step import get_google_service + +async def clear_all_calendars(): + """Clear all events from all calendars""" + try: + service = await get_google_service() + + # Get all calendars + print("Fetching calendar list...") + calendars_result = service.calendarList().list().execute() + calendars = calendars_result.get('items', []) + + print(f"Raw calendars result: {calendars_result}") + print(f"Calendars list: {calendars}") + + print(f"Found {len(calendars)} calendars to clear:") + + for calendar in calendars: + calendar_id = calendar['id'] + summary = calendar.get('summary', 'No summary') + primary = calendar.get('primary', False) + + print(f" - {summary} (ID: {calendar_id}, Primary: {primary})") + + try: + # Get all events in this calendar + events_result = service.events().list(calendarId=calendar_id, singleEvents=True).execute() + events = events_result.get('items', []) + + print(f" Found {len(events)} events to delete") + + # Delete each event + deleted_count = 0 + for event in events: + try: + service.events().delete(calendarId=calendar_id, eventId=event['id']).execute() + deleted_count += 1 + except Exception as e: + print(f" Failed to delete event {event['id']}: {e}") + + print(f" ✓ Deleted {deleted_count} events from: {summary}") + + except Exception as e: + print(f" ✗ Failed to clear {summary}: {e}") + + print("\nAll calendars have been cleared of events.") + + except Exception as e: + print(f"Error: {e}") + +if __name__ == "__main__": + asyncio.run(clear_all_calendars()) \ No newline at end of file diff --git a/bitbylaw/steps/advoware_cal_sync/calendar_sync_event_step.py b/bitbylaw/steps/advoware_cal_sync/calendar_sync_event_step.py index 43419072..e569c239 100644 --- a/bitbylaw/steps/advoware_cal_sync/calendar_sync_event_step.py +++ b/bitbylaw/steps/advoware_cal_sync/calendar_sync_event_step.py @@ -614,9 +614,14 @@ async def handler(event, context): ) logger.info(f"Fetched {len(rows)} existing sync rows") - # Build indexes + # Build indexes - use recurringEventId for recurring events to avoid duplicates db_adv_index = {str(row['advoware_frnr']): row for row in rows if row['advoware_frnr']} - db_google_index = {row['google_event_id']: row for row in rows if row['google_event_id']} + db_google_index = {} + for row in rows: + if row['google_event_id']: + # For recurring events, use the master event ID (recurringEventId) + # For regular events, use the event_id directly + db_google_index[row['google_event_id']] = row # Phase 1: New from Advoware => Google logger.info("Phase 1: Processing new appointments from Advoware") @@ -640,7 +645,12 @@ async def handler(event, context): # Phase 2: New from Google => Advoware logger.info("Phase 2: Processing new events from Google") for event_id, evt in google_map.items(): - if event_id not in db_google_index: + # For recurring events, check if the master event (recurringEventId) is already synced + # For regular events, check the event_id directly + recurring_master_id = evt.get('recurringEventId') + is_already_synced = event_id in db_google_index or (recurring_master_id and recurring_master_id in db_google_index) + + if not is_already_synced: try: frnr = await safe_create_advoware_appointment(advoware, standardize_appointment_data(evt, 'google'), kuerzel, True) if frnr and str(frnr) != 'None': @@ -664,7 +674,19 @@ async def handler(event, context): frnr = row['advoware_frnr'] event_id = row['google_event_id'] adv_exists = str(frnr) in adv_map if frnr else False - google_exists = event_id in google_map if event_id else False + + # For Google events, check if the master event or any instance exists + google_exists = False + if event_id: + # Check if the stored event_id exists + if event_id in google_map: + google_exists = True + else: + # Check if any event has this as recurringEventId (master event still exists) + for evt in google_map.values(): + if evt.get('recurringEventId') == event_id: + google_exists = True + break if not adv_exists and not google_exists: # Both missing - soft delete @@ -773,11 +795,36 @@ async def handler(event, context): # Phase 4: Update existing entries if changed logger.info("Phase 4: Processing updates for existing entries") + # Track which master events we've already processed to avoid duplicate updates + processed_master_events = set() + for row in rows: frnr = row['advoware_frnr'] event_id = row['google_event_id'] adv_data = adv_map.get(str(frnr)) if frnr else None - google_data = google_map.get(event_id) if event_id else None + + # For Google events, find the corresponding event (could be master or instance) + google_data = None + if event_id: + # First try to find the exact event_id + if event_id in google_map: + google_data = google_map[event_id] + else: + # Look for any event that has this as recurringEventId + for evt in google_map.values(): + if evt.get('recurringEventId') == event_id: + google_data = evt + break + + # Skip if we don't have both sides or if we've already processed this master event + if not adv_data or not google_data: + continue + + # For recurring events, only process the master event once + master_event_id = google_data.get('recurringEventId') or event_id + if master_event_id in processed_master_events: + continue + processed_master_events.add(master_event_id) if adv_data and google_data: adv_std = standardize_appointment_data(adv_data, 'advoware')