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 <noreply@anthropic.com>
This commit is contained in:
mARTin 2026-03-24 10:51:03 +01:00
parent c718aa584a
commit 18c2fc1a92
2 changed files with 58 additions and 37 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "my-weekly-todo-list", "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", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -341,44 +341,54 @@ export const updateEvent = async (
eventId: string, eventId: string,
event: any event: any
) => { ) => {
const response = await fetch(`${GRAPH_ENDPOINT}/me/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`, { const body = JSON.stringify({
method: 'PATCH', subject: event.summary,
headers: { body: {
'Authorization': `Bearer ${accessToken}`, contentType: 'HTML',
'Content-Type': 'application/json' content: event.description || ''
}, },
body: JSON.stringify({ start: ensureTimeZone(event.start),
subject: event.summary, end: ensureTimeZone(event.end),
body: { isAllDay: event.allDay !== undefined ? !!event.allDay : undefined,
contentType: 'HTML', location: {
content: event.description || '' displayName: event.location || ''
}, },
start: ensureTimeZone(event.start), ...(event.recurrence ? { recurrence: event.recurrence } : {}),
end: ensureTimeZone(event.end), ...(event.reminders?.length ? {
isAllDay: event.allDay !== undefined ? !!event.allDay : undefined, isReminderOn: true,
location: { reminderMinutesBeforeStart: event.reminders[0].minutes,
displayName: event.location || '' } : {}),
}, ...(event.busyStatus ? { showAs: event.busyStatus === 'oof' ? 'oof' : event.busyStatus } : {}),
...(event.recurrence ? { recurrence: event.recurrence } : {}), ...(event.visibility ? {
...(event.reminders?.length ? { sensitivity: event.visibility === 'private' ? 'private'
isReminderOn: true, : event.visibility === 'confidential' ? 'confidential'
reminderMinutesBeforeStart: event.reminders[0].minutes, : 'normal'
} : {}), } : {}),
...(event.busyStatus ? { showAs: event.busyStatus === 'oof' ? 'oof' : event.busyStatus } : {}), ...(event.attendees?.length ? {
...(event.visibility ? { attendees: event.attendees.map((a: any) => ({
sensitivity: event.visibility === 'private' ? 'private' emailAddress: { address: a.email, name: a.displayName || a.email },
: event.visibility === 'confidential' ? 'confidential' type: 'required',
: '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) { if (!response.ok) {
const err = await response.text(); const err = await response.text();
throw new Error(`Failed to update Outlook event: ${err}`); throw new Error(`Failed to update Outlook event: ${err}`);
@ -404,13 +414,24 @@ export const deleteEvent = async (
calendarId: string, calendarId: string,
eventId: 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', method: 'DELETE',
headers: { headers: {
'Authorization': `Bearer ${accessToken}` '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) { if (!response.ok) {
const err = await response.text(); const err = await response.text();
throw new Error(`Failed to delete Outlook event: ${err}`); throw new Error(`Failed to delete Outlook event: ${err}`);