From 18c2fc1a9266f3b7f75e48f9fdf6074c097aaeb2 Mon Sep 17 00:00:00 2001 From: mARTin Date: Tue, 24 Mar 2026 10:51:03 +0100 Subject: [PATCH] fix: Outlook update/delete fallback to non-calendar-scoped endpoint When the calendar-scoped Graph API endpoint returns 404 for series master or instance IDs, fall back to /me/events/{id} which handles these cases. Fixes ErrorItemNotFound on delete/update of recurring Outlook events. v1.63.5 Co-Authored-By: Claude Opus 4.6 --- package.json | 2 +- src/lib/outlook-calendar.ts | 93 +++++++++++++++++++++++-------------- 2 files changed, 58 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index de80678..d8107f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.63.4", + "version": "1.63.5", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "main": "index.js", "scripts": { diff --git a/src/lib/outlook-calendar.ts b/src/lib/outlook-calendar.ts index 79e511f..bdf71c4 100644 --- a/src/lib/outlook-calendar.ts +++ b/src/lib/outlook-calendar.ts @@ -341,44 +341,54 @@ export const updateEvent = async ( eventId: string, event: any ) => { - const response = await fetch(`${GRAPH_ENDPOINT}/me/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`, { - method: 'PATCH', - headers: { - 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/json' + const body = JSON.stringify({ + subject: event.summary, + body: { + contentType: 'HTML', + content: event.description || '' }, - body: JSON.stringify({ - subject: event.summary, - body: { - contentType: 'HTML', - content: event.description || '' - }, - start: ensureTimeZone(event.start), - end: ensureTimeZone(event.end), - isAllDay: event.allDay !== undefined ? !!event.allDay : undefined, - location: { - displayName: event.location || '' - }, - ...(event.recurrence ? { recurrence: event.recurrence } : {}), - ...(event.reminders?.length ? { - isReminderOn: true, - reminderMinutesBeforeStart: event.reminders[0].minutes, - } : {}), - ...(event.busyStatus ? { showAs: event.busyStatus === 'oof' ? 'oof' : event.busyStatus } : {}), - ...(event.visibility ? { - sensitivity: event.visibility === 'private' ? 'private' - : event.visibility === 'confidential' ? 'confidential' - : 'normal' - } : {}), - ...(event.attendees?.length ? { - attendees: event.attendees.map((a: any) => ({ - emailAddress: { address: a.email, name: a.displayName || a.email }, - type: 'required', - })), - } : {}), - }) + start: ensureTimeZone(event.start), + end: ensureTimeZone(event.end), + isAllDay: event.allDay !== undefined ? !!event.allDay : undefined, + location: { + displayName: event.location || '' + }, + ...(event.recurrence ? { recurrence: event.recurrence } : {}), + ...(event.reminders?.length ? { + isReminderOn: true, + reminderMinutesBeforeStart: event.reminders[0].minutes, + } : {}), + ...(event.busyStatus ? { showAs: event.busyStatus === 'oof' ? 'oof' : event.busyStatus } : {}), + ...(event.visibility ? { + sensitivity: event.visibility === 'private' ? 'private' + : event.visibility === 'confidential' ? 'confidential' + : 'normal' + } : {}), + ...(event.attendees?.length ? { + attendees: event.attendees.map((a: any) => ({ + emailAddress: { address: a.email, name: a.displayName || a.email }, + type: 'required', + })), + } : {}), }); + const patchHeaders = { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': 'application/json' + }; + + // Try calendar-scoped endpoint first + let response = await fetch(`${GRAPH_ENDPOINT}/me/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`, { + method: 'PATCH', headers: patchHeaders, body + }); + + if (!response.ok && response.status === 404) { + // Fallback: try non-calendar-scoped endpoint + response = await fetch(`${GRAPH_ENDPOINT}/me/events/${encodeURIComponent(eventId)}`, { + method: 'PATCH', headers: patchHeaders, body + }); + } + if (!response.ok) { const err = await response.text(); throw new Error(`Failed to update Outlook event: ${err}`); @@ -404,13 +414,24 @@ export const deleteEvent = async ( calendarId: string, eventId: string ) => { - const response = await fetch(`${GRAPH_ENDPOINT}/me/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`, { + // Try calendar-scoped endpoint first + let response = await fetch(`${GRAPH_ENDPOINT}/me/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${accessToken}` } }); + if (!response.ok && response.status === 404) { + // Fallback: try non-calendar-scoped endpoint (works better for series master IDs) + response = await fetch(`${GRAPH_ENDPOINT}/me/events/${encodeURIComponent(eventId)}`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${accessToken}` + } + }); + } + if (!response.ok) { const err = await response.text(); throw new Error(`Failed to delete Outlook event: ${err}`);