From 6fefc74fd5ddd2fe290f26bac5d81ba9b23706f9 Mon Sep 17 00:00:00 2001 From: alma Date: Thu, 15 Jan 2026 14:26:32 +0100 Subject: [PATCH] Agenda refactor --- lib/services/microsoft-calendar-sync.ts | 183 +++++++++++++++++++++++- 1 file changed, 176 insertions(+), 7 deletions(-) diff --git a/lib/services/microsoft-calendar-sync.ts b/lib/services/microsoft-calendar-sync.ts index 001b4bb..6f9bf3b 100644 --- a/lib/services/microsoft-calendar-sync.ts +++ b/lib/services/microsoft-calendar-sync.ts @@ -266,6 +266,60 @@ export async function fetchMicrosoftEvents( errorDetails.params = error.config?.params; } + // If calendar not found (404), try using the default calendar instead + if (error.response?.status === 404 && error.response?.data?.error?.code === 'ErrorItemNotFound') { + logger.warn('Calendar not found, trying default calendar', { + userId, + email, + oldCalendarId: calendarId, + }); + + // Try using the default calendar endpoint + try { + const accessToken = await getMicrosoftGraphClient(userId, email); + const defaultUrl = 'https://graph.microsoft.com/v1.0/me/calendar/events'; + + const params: any = { + $select: 'id,subject,body,start,end,location,isAllDay', + $orderby: 'start/dateTime asc', + $top: 1000, + }; + + if (startDate && endDate) { + const startDateTimeStr = startDate.toISOString(); + const endDateTimeStr = endDate.toISOString(); + params.$filter = `start/dateTime ge '${startDateTimeStr}' and start/dateTime le '${endDateTimeStr}'`; + } + + logger.info('Fetching from default calendar', { + url: defaultUrl, + params: JSON.stringify(params), + }); + + const response = await axios.get(defaultUrl, { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + params, + }); + + const events = response.data.value || []; + logger.info('Successfully fetched from default calendar', { + eventCount: events.length, + }); + + return events; + } catch (fallbackError: any) { + logger.error('Failed to fetch from default calendar', { + userId, + email, + error: fallbackError instanceof Error ? fallbackError.message : String(fallbackError), + }); + // Continue to throw original error + } + } + logger.error('Error fetching Microsoft events', errorDetails); throw error; } @@ -383,13 +437,128 @@ export async function syncMicrosoftCalendar( dateRange: { start: startDate.toISOString(), end: endDate.toISOString() }, }); - const microsoftEvents = await fetchMicrosoftEvents( - syncConfig.calendar.userId, - creds.email, - syncConfig.externalCalendarId || '', - startDate, - endDate - ); + // Fetch events from Microsoft Graph API + // If calendar ID is invalid (404), we'll try to discover and update it + let microsoftEvents: MicrosoftEvent[]; + let calendarIdToUse = syncConfig.externalCalendarId || ''; + + try { + microsoftEvents = await fetchMicrosoftEvents( + syncConfig.calendar.userId, + creds.email, + calendarIdToUse, + startDate, + endDate + ); + } catch (error: any) { + // If calendar not found (404), try to discover available calendars and update + if (error.response?.status === 404 && error.response?.data?.error?.code === 'ErrorItemNotFound') { + logger.warn('Calendar ID not found, discovering available calendars', { + calendarSyncId, + oldCalendarId: calendarIdToUse, + email: creds.email, + }); + + // Discover available calendars + const availableCalendars = await discoverMicrosoftCalendars( + syncConfig.calendar.userId, + creds.email + ); + + if (availableCalendars.length > 0) { + // Use the first calendar (usually the default "Calendar") + const newCalendar = availableCalendars[0]; + calendarIdToUse = newCalendar.id; + + logger.info('Updating calendar sync with new calendar ID', { + calendarSyncId, + oldCalendarId: syncConfig.externalCalendarId, + newCalendarId: calendarIdToUse, + newCalendarName: newCalendar.name, + }); + + // Update the sync config with the new calendar ID + await prisma.calendarSync.update({ + where: { id: calendarSyncId }, + data: { + externalCalendarId: calendarIdToUse, + lastSyncError: null, // Clear previous error + }, + }); + + // Retry fetching events with the new calendar ID + microsoftEvents = await fetchMicrosoftEvents( + syncConfig.calendar.userId, + creds.email, + calendarIdToUse, + startDate, + endDate + ); + } else { + // No calendars found, try using default calendar endpoint + logger.warn('No calendars discovered, using default calendar endpoint', { + calendarSyncId, + email: creds.email, + }); + + // Use default calendar by fetching without a specific calendar ID + const accessToken = await getMicrosoftGraphClient(syncConfig.calendar.userId, creds.email); + const defaultUrl = 'https://graph.microsoft.com/v1.0/me/calendar/events'; + + const params: any = { + $select: 'id,subject,body,start,end,location,isAllDay', + $orderby: 'start/dateTime asc', + $top: 1000, + }; + + const startDateTimeStr = startDate.toISOString(); + const endDateTimeStr = endDate.toISOString(); + params.$filter = `start/dateTime ge '${startDateTimeStr}' and start/dateTime le '${endDateTimeStr}'`; + + const response = await axios.get(defaultUrl, { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + params, + }); + + microsoftEvents = response.data.value || []; + + // Try to get the default calendar ID for future use + try { + const calendarResponse = await axios.get('https://graph.microsoft.com/v1.0/me/calendar', { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }); + + const defaultCalendarId = calendarResponse.data.id; + if (defaultCalendarId) { + await prisma.calendarSync.update({ + where: { id: calendarSyncId }, + data: { + externalCalendarId: defaultCalendarId, + lastSyncError: null, + }, + }); + logger.info('Updated sync config with default calendar ID', { + calendarSyncId, + defaultCalendarId, + }); + } + } catch (calendarIdError) { + logger.warn('Could not fetch default calendar ID', { + error: calendarIdError instanceof Error ? calendarIdError.message : String(calendarIdError), + }); + } + } + } else { + // Re-throw other errors + throw error; + } + } // Log all events with full details logger.info('Fetched Microsoft events', {