fix: deduplicate UIDs during iCal import to prevent mass failures #113
This commit is contained in:
@@ -18,7 +18,7 @@ interface ICalImportModalProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
|
||||
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
||||
const ACCEPTED_EXTENSIONS = [".ics", ".ical"];
|
||||
|
||||
type ImportStep = "select" | "preview" | "importing";
|
||||
|
||||
@@ -2057,7 +2057,7 @@
|
||||
"importing": "Termine werden importiert...",
|
||||
"success": "{count} Termine erfolgreich importiert",
|
||||
"error": "Kalender konnte nicht importiert werden",
|
||||
"file_too_large": "Datei überschreitet das 5-MB-Limit",
|
||||
"file_too_large": "Datei überschreitet das 10-MB-Limit",
|
||||
"invalid_format": "Ungültiges Kalenderdateiformat"
|
||||
},
|
||||
"management": {
|
||||
|
||||
@@ -2057,7 +2057,7 @@
|
||||
"importing": "Importing events...",
|
||||
"success": "{count} events imported successfully",
|
||||
"error": "Failed to import calendar",
|
||||
"file_too_large": "File exceeds 5MB limit",
|
||||
"file_too_large": "File exceeds 10MB limit",
|
||||
"invalid_format": "Invalid calendar file format"
|
||||
},
|
||||
"management": {
|
||||
|
||||
@@ -2057,7 +2057,7 @@
|
||||
"importing": "Importando eventos...",
|
||||
"success": "{count} eventos importados correctamente",
|
||||
"error": "Error al importar el calendario",
|
||||
"file_too_large": "El archivo supera el límite de 5 MB",
|
||||
"file_too_large": "El archivo supera el límite de 10 MB",
|
||||
"invalid_format": "Formato de archivo de calendario no válido"
|
||||
},
|
||||
"management": {
|
||||
|
||||
@@ -2057,7 +2057,7 @@
|
||||
"importing": "Importation en cours...",
|
||||
"success": "{count} événements importés avec succès",
|
||||
"error": "Échec de l'importation du calendrier",
|
||||
"file_too_large": "Le fichier dépasse la limite de 5 Mo",
|
||||
"file_too_large": "Le fichier dépasse la limite de 10 Mo",
|
||||
"invalid_format": "Format de fichier calendrier invalide"
|
||||
},
|
||||
"management": {
|
||||
|
||||
@@ -2057,7 +2057,7 @@
|
||||
"importing": "Importazione eventi...",
|
||||
"success": "{count} eventi importati con successo",
|
||||
"error": "Importazione del calendario fallita",
|
||||
"file_too_large": "Il file supera il limite di 5 MB",
|
||||
"file_too_large": "Il file supera il limite di 10 MB",
|
||||
"invalid_format": "Formato del file calendario non valido"
|
||||
},
|
||||
"management": {
|
||||
|
||||
@@ -2057,7 +2057,7 @@
|
||||
"importing": "イベントをインポート中...",
|
||||
"success": "{count}件のイベントをインポートしました",
|
||||
"error": "カレンダーのインポートに失敗しました",
|
||||
"file_too_large": "ファイルサイズが5MBを超えています",
|
||||
"file_too_large": "ファイルサイズが10MBを超えています",
|
||||
"invalid_format": "無効なカレンダーファイル形式"
|
||||
},
|
||||
"management": {
|
||||
|
||||
@@ -2057,7 +2057,7 @@
|
||||
"importing": "Evenementen importeren...",
|
||||
"success": "{count} evenementen succesvol geïmporteerd",
|
||||
"error": "Agenda importeren mislukt",
|
||||
"file_too_large": "Bestand overschrijdt de limiet van 5 MB",
|
||||
"file_too_large": "Bestand overschrijdt de limiet van 10 MB",
|
||||
"invalid_format": "Ongeldig agendabestandsformaat"
|
||||
},
|
||||
"management": {
|
||||
|
||||
@@ -2057,7 +2057,7 @@
|
||||
"importing": "Importando eventos...",
|
||||
"success": "{count} eventos importados com sucesso",
|
||||
"error": "Falha ao importar calendário",
|
||||
"file_too_large": "Arquivo excede o limite de 5 MB",
|
||||
"file_too_large": "Arquivo excede o limite de 10 MB",
|
||||
"invalid_format": "Formato de arquivo de calendário inválido"
|
||||
},
|
||||
"management": {
|
||||
|
||||
@@ -2057,7 +2057,7 @@
|
||||
"importing": "Импортирование событий...",
|
||||
"success": "{count} событий успешно импортировано",
|
||||
"error": "Не удалось импортировать календарь",
|
||||
"file_too_large": "Файл превышает лимит 5 МБ",
|
||||
"file_too_large": "Файл превышает лимит 10 МБ",
|
||||
"invalid_format": "Неверный формат файла календаря"
|
||||
},
|
||||
"management": {
|
||||
|
||||
@@ -383,9 +383,28 @@ 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)
|
||||
let eventsToProcess = events;
|
||||
try {
|
||||
const allServerEvents = await client.getCalendarEvents(undefined, targetAccountId);
|
||||
const existingUids = new Set<string>();
|
||||
for (const e of allServerEvents) {
|
||||
if (e.uid) existingUids.add(e.uid);
|
||||
}
|
||||
const before = eventsToProcess.length;
|
||||
eventsToProcess = eventsToProcess.filter(e => !e.uid || !existingUids.has(e.uid));
|
||||
const skipped = before - eventsToProcess.length;
|
||||
if (skipped > 0) {
|
||||
debug.log(`Import: skipped ${skipped} events with duplicate UIDs (already on server)`);
|
||||
}
|
||||
} catch (error) {
|
||||
debug.warn('Could not fetch existing events for deduplication, proceeding without:', error);
|
||||
}
|
||||
|
||||
// Prepare all events for batch creation
|
||||
const prepared: Partial<CalendarEvent>[] = [];
|
||||
for (const event of events) {
|
||||
for (const event of eventsToProcess) {
|
||||
const src = sanitizeOutgoingCalendarEventData(event as Partial<CalendarEvent>);
|
||||
let cleanParticipants: Record<string, CalendarParticipant> | null = null;
|
||||
if (src.participants) {
|
||||
|
||||
Reference in New Issue
Block a user