fix: link existing events to target calendar during iCal import instead of skipping #113

This commit is contained in:
Linus Rath
2026-03-30 16:47:08 +02:00
parent 67210c9924
commit 63b464641b
+52 -10
View File
@@ -383,21 +383,63 @@ export const useCalendarStore = create<CalendarStore>()(
const realCalendarId = cal?.originalId || calendarId;
const targetAccountId = cal?.accountId;
// Deduplicate: skip events whose UIDs already exist on the server
// (Stalwart enforces UID uniqueness across all calendars)
// Deduplicate UIDs: Stalwart enforces UID uniqueness across all calendars.
// - Events already in the target calendar → skip (true duplicates)
// - Events in other calendars → link to target calendar via calendarIds update
// - New events → create as normal
let eventsToProcess = events;
let linked = 0;
try {
const allServerEvents = await client.getCalendarEvents(undefined, targetAccountId);
const existingUids = new Set<string>();
const uidToEvent = new Map<string, { id: string; calendarIds: Record<string, boolean> }>();
for (const e of allServerEvents) {
if (e.uid) existingUids.add(e.uid);
if (e.uid) {
uidToEvent.set(e.uid, {
id: (e as CalendarEvent).originalId || e.id,
calendarIds: (e as CalendarEvent).originalCalendarIds || e.calendarIds || {},
});
}
}
const before = eventsToProcess.length;
eventsToProcess = eventsToProcess.filter(e => !e.uid || !existingUids.has(e.uid));
const skipped = before - eventsToProcess.length;
const newEvents: Partial<CalendarEvent>[] = [];
const eventsToLink: { eventId: string; calendarIds: Record<string, boolean> }[] = [];
for (const e of eventsToProcess) {
if (!e.uid || !uidToEvent.has(e.uid)) {
// UID doesn't exist on server — create it
newEvents.push(e);
} else {
const existing = uidToEvent.get(e.uid)!;
if (existing.calendarIds[realCalendarId]) {
// Already in target calendar — skip
continue;
}
// Exists in another calendar — link to target calendar
eventsToLink.push({
eventId: existing.id,
calendarIds: { ...existing.calendarIds, [realCalendarId]: true },
});
}
}
// Batch-link existing events to the target calendar
for (const { eventId, calendarIds } of eventsToLink) {
try {
await client.updateCalendarEvent(eventId, { calendarIds } as Partial<CalendarEvent>, undefined, targetAccountId);
linked++;
} catch (err) {
debug.warn(`Import: failed to link event ${eventId} to target calendar:`, err);
}
}
if (linked > 0) {
debug.log(`Import: linked ${linked} existing events to target calendar`);
}
const skipped = eventsToProcess.length - newEvents.length - eventsToLink.length;
if (skipped > 0) {
debug.log(`Import: skipped ${skipped} events with duplicate UIDs (already on server)`);
debug.log(`Import: skipped ${skipped} events already in target calendar`);
}
eventsToProcess = newEvents;
} catch (error) {
debug.warn('Could not fetch existing events for deduplication, proceeding without:', error);
}
@@ -473,7 +515,7 @@ export const useCalendarStore = create<CalendarStore>()(
prepared.push(data);
}
if (prepared.length === 0) return 0;
if (prepared.length === 0) return linked;
// Batch create in chunks of 50 to avoid oversized requests
const BATCH_SIZE = 50;
@@ -498,7 +540,7 @@ export const useCalendarStore = create<CalendarStore>()(
await get().fetchEvents(client, dateRange.start, dateRange.end);
}
return imported;
return imported + linked;
},
deleteEvent: async (client, id, sendSchedulingMessages) => {