From 631524b8a805204a9d18f8bcd76ea8977b268e8a Mon Sep 17 00:00:00 2001 From: alma Date: Thu, 15 Jan 2026 13:22:43 +0100 Subject: [PATCH] Agenda refactor --- app/agenda/page.tsx | 6 ++-- app/api/calendars/route.ts | 17 +++++++--- lib/services/microsoft-calendar-sync.ts | 44 +++++++++++++++++++++++-- 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/app/agenda/page.tsx b/app/agenda/page.tsx index aff67eb..6642c10 100644 --- a/app/agenda/page.tsx +++ b/app/agenda/page.tsx @@ -378,14 +378,14 @@ export default async function CalendarPage() { // Trigger sync for Microsoft calendars that need it (async, don't wait) for (const syncConfig of microsoftSyncConfigs) { - // For Microsoft, use a more frequent check (2 minutes) for better reactivity + // For Microsoft, use a more frequent check (1 minute) for better reactivity // This allows new events to appear faster without overloading the API - const microsoftMinSyncInterval = 2; // minutes + const microsoftMinSyncInterval = 1; // minutes (reduced from 2 to 1 for faster sync) const minutesSinceLastSync = syncConfig.lastSyncAt ? (Date.now() - syncConfig.lastSyncAt.getTime()) / (1000 * 60) : Infinity; - // Sync if never synced, or if enough time has passed (use minimum of 2 min or configured frequency) + // Sync if never synced, or if enough time has passed (use minimum of 1 min or configured frequency) const needsSync = !syncConfig.lastSyncAt || minutesSinceLastSync >= Math.min(microsoftMinSyncInterval, syncConfig.syncFrequency); diff --git a/app/api/calendars/route.ts b/app/api/calendars/route.ts index d3cafa5..8021810 100644 --- a/app/api/calendars/route.ts +++ b/app/api/calendars/route.ts @@ -98,22 +98,31 @@ export async function GET(req: NextRequest) { }); // Extract mission calendars (excluding those already in personalCalendars) + // Use a Set to avoid duplicate calendars by ID + const personalCalendarIds = new Set(personalCalendars.map(cal => cal.id)); const missionCalendars = missionUserRelations .flatMap(mu => mu.mission.calendars) - .filter(cal => cal.userId !== session.user.id); // Exclude calendars owned by user (already in personalCalendars) + .filter(cal => !personalCalendarIds.has(cal.id)); // Exclude calendars already in personalCalendars // Combine personal and mission calendars const calendars = [...personalCalendars, ...missionCalendars]; + + // Remove duplicate calendars by ID (in case same calendar appears multiple times) + const uniqueCalendars = Array.from( + new Map(calendars.map(cal => [cal.id, cal])).values() + ); logger.debug('[CALENDAR] Fetched calendars with events', { userId: session.user.id, - count: calendars.length, + personalCount: personalCalendars.length, + missionCount: missionCalendars.length, + totalCount: uniqueCalendars.length, }); // Cache the results - await cacheCalendarData(session.user.id, calendars); + await cacheCalendarData(session.user.id, uniqueCalendars); - return NextResponse.json(calendars); + return NextResponse.json(uniqueCalendars); } catch (error) { logger.error('[CALENDAR] Erreur lors de la récupération des calendriers', { error: error instanceof Error ? error.message : String(error), diff --git a/lib/services/microsoft-calendar-sync.ts b/lib/services/microsoft-calendar-sync.ts index ef324d7..d8ca774 100644 --- a/lib/services/microsoft-calendar-sync.ts +++ b/lib/services/microsoft-calendar-sync.ts @@ -477,6 +477,14 @@ export async function syncMicrosoftCalendar( ? existingEventsByExternalId.get(microsoftId) : undefined; + if (existingEvent) { + logger.debug('Matched event by externalEventId', { + microsoftId, + eventId: existingEvent.id, + title: caldavEvent.summary, + }); + } + // Priority 2: Fallback to checking description for [MS_ID:xxx] (backward compatibility) if (!existingEvent && microsoftId) { existingEvent = existingEvents.find((e) => { @@ -487,23 +495,53 @@ export async function syncMicrosoftCalendar( } return false; }); + + if (existingEvent) { + logger.debug('Matched event by description [MS_ID]', { + microsoftId, + eventId: existingEvent.id, + title: caldavEvent.summary, + }); + } } // Priority 3: Fallback to title + date matching for events without externalEventId + // IMPORTANT: Only match if the event doesn't have an externalEventId (to avoid false matches) if (!existingEvent) { existingEvent = existingEvents.find( (e) => { // Access externalEventId safely (may not be in Prisma type if client not regenerated) const hasExternalId = !!(e as any).externalEventId; - if (!hasExternalId && // Only match events that don't have externalEventId yet - e.title === caldavEvent.summary) { + // Only match events that don't have externalEventId yet (to avoid false matches) + if (hasExternalId) { + return false; // Skip events that already have externalEventId + } + + // Match by title and date (within 1 minute) + if (e.title === caldavEvent.summary) { const timeDiff = Math.abs(new Date(e.start).getTime() - caldavEvent.start.getTime()); - return timeDiff < 60000; // Within 1 minute + if (timeDiff < 60000) { // Within 1 minute + logger.debug('Matched event by title + date (no externalEventId)', { + eventId: e.id, + title: caldavEvent.summary, + timeDiff, + }); + return true; + } } return false; } ); } + + // Log if no match found (new event) + if (!existingEvent) { + logger.debug('No match found, will create new event', { + microsoftId, + title: caldavEvent.summary, + start: caldavEvent.start.toISOString(), + }); + } // Clean description (remove [MS_ID:xxx] prefix if present from previous syncs) const cleanedDescription = cleanDescription(caldavEvent.description);