From 0a16f15aa83cb0c571f74c599f3a6bf76feceda3 Mon Sep 17 00:00:00 2001 From: alma Date: Thu, 15 Jan 2026 13:07:26 +0100 Subject: [PATCH] Agenda refactor --- app/agenda/page.tsx | 447 +------------------------------------------- 1 file changed, 1 insertion(+), 446 deletions(-) diff --git a/app/agenda/page.tsx b/app/agenda/page.tsx index 1c5be71..13a590e 100644 --- a/app/agenda/page.tsx +++ b/app/agenda/page.tsx @@ -90,26 +90,7 @@ export default async function CalendarPage() { } }); - // Auto-setup sync for email accounts from courrier (Infomaniak and Microsoft) - // Get all Infomaniak email accounts - const infomaniakAccounts = await prisma.mailCredentials.findMany({ - where: { - userId: session?.user?.id || '', - host: { - contains: 'infomaniak' - }, - password: { - not: null - } - }, - select: { - id: true, - email: true, - display_name: true, - password: true - } - }); - + // Auto-setup sync for email accounts from courrier (Microsoft only) // Get all Microsoft email accounts (OAuth) const microsoftAccounts = await prisma.mailCredentials.findMany({ where: { @@ -134,7 +115,6 @@ export default async function CalendarPage() { // Clean up orphaned syncs FIRST (before creating new ones) // This handles the case where a user deleted and re-added an email account const allMailCredentialIds = new Set([ - ...infomaniakAccounts.map(acc => acc.id), ...microsoftAccounts.map(acc => acc.id) ]); @@ -175,300 +155,6 @@ export default async function CalendarPage() { } } - // For each Infomaniak account, ensure there's a synced calendar - // Skip if no Infomaniak accounts exist (user may only have Microsoft accounts) - if (infomaniakAccounts.length > 0) { - for (const account of infomaniakAccounts) { - // Check if a calendar sync already exists for this account (enabled or disabled) - // This prevents creating duplicate calendars for the same account - let existingSync = await prisma.calendarSync.findFirst({ - where: { - mailCredentialId: account.id - }, - include: { - calendar: true - } - }); - - // If no sync found by mailCredentialId, check if there's an orphaned sync with same email - // This handles the case where user deleted and re-added the account (new mailCredentialId, same email) - if (!existingSync) { - const orphanedSyncByEmail = await prisma.calendarSync.findFirst({ - where: { - provider: 'infomaniak', - calendar: { - userId: session?.user?.id || '', - name: 'Privée' - }, - mailCredential: { - email: account.email - } - }, - include: { - calendar: true, - mailCredential: true - } - }); - - if (orphanedSyncByEmail && orphanedSyncByEmail.mailCredential?.email === account.email) { - console.log(`[AGENDA] Found orphaned Infomaniak sync for email ${account.email}, reassigning to new mailCredentialId ${account.id}`); - // Reassign the sync to the new mailCredentialId - await prisma.calendarSync.update({ - where: { id: orphanedSyncByEmail.id }, - data: { - mailCredentialId: account.id, - syncEnabled: true - } - }); - // Reload the sync - existingSync = await prisma.calendarSync.findFirst({ - where: { - mailCredentialId: account.id - }, - include: { - calendar: true - } - }); - } - } - - // If sync exists but is disabled, check if it's due to invalid credentials - // Don't re-enable if the last error was 401 (invalid credentials) - if (existingSync) { - console.log(`[AGENDA] Found existing sync for Infomaniak account ${account.email}: syncId=${existingSync.id}, calendarId=${existingSync.calendarId}, syncEnabled=${existingSync.syncEnabled}, hasCalendar=${!!existingSync.calendar}, externalCalendarUrl=${existingSync.externalCalendarUrl}`); - - // Fix invalid calendar URLs (like /principals) - if (existingSync.externalCalendarUrl === '/principals' || !existingSync.externalCalendarUrl || existingSync.externalCalendarUrl === '/') { - console.log(`[AGENDA] Invalid calendar URL detected (${existingSync.externalCalendarUrl}), attempting to rediscover...`); - try { - const { discoverInfomaniakCalendars } = await import('@/lib/services/caldav-sync'); - - if (!account.password) { - console.error(`[AGENDA] Cannot rediscover calendars: missing password for ${account.email}`); - } else { - const externalCalendars = await discoverInfomaniakCalendars( - account.email, - account.password - ); - - console.log(`[AGENDA] Rediscovery result: found ${externalCalendars.length} calendars for ${account.email}`); - - if (externalCalendars.length > 0) { - const mainCalendar = externalCalendars[0]; - console.log(`[AGENDA] Updating sync with correct calendar URL: ${mainCalendar.url} (name: ${mainCalendar.name})`); - await prisma.calendarSync.update({ - where: { id: existingSync.id }, - data: { - externalCalendarId: mainCalendar.id, - externalCalendarUrl: mainCalendar.url, - lastSyncError: null, // Clear error since we're fixing the URL - } - }); - // Reload the sync config - const updatedSync = await prisma.calendarSync.findUnique({ - where: { id: existingSync.id }, - include: { calendar: true } - }); - if (updatedSync) { - existingSync = updatedSync; - console.log(`[AGENDA] Sync config updated successfully, new URL: ${updatedSync.externalCalendarUrl}`); - } - } else { - console.warn(`[AGENDA] No calendars found during rediscovery for ${account.email}. This may indicate:`); - console.warn(` - Authentication issue (wrong password)`); - console.warn(` - No calendars exist for this account`); - console.warn(` - CalDAV server issue`); - // Mark sync as having an error so user knows something is wrong - await prisma.calendarSync.update({ - where: { id: existingSync.id }, - data: { - lastSyncError: `No calendars found during rediscovery. Please check your Infomaniak account credentials and calendar access.`, - } - }); - } - } - } catch (rediscoverError: any) { - console.error(`[AGENDA] Error rediscovering calendars for ${account.email}:`, { - error: rediscoverError?.message || String(rediscoverError), - stack: rediscoverError?.stack?.substring(0, 300), - }); - // Mark sync as having an error - await prisma.calendarSync.update({ - where: { id: existingSync.id }, - data: { - lastSyncError: `Rediscovery failed: ${rediscoverError?.message || 'Unknown error'}`, - } - }).catch(() => { - // Ignore update errors - }); - } - } - - // Check if calendar still exists - if (!existingSync.calendar) { - console.log(`[AGENDA] Calendar for sync ${existingSync.id} does not exist, creating new calendar`); - // Calendar was deleted, create a new one - const calendar = await prisma.calendar.create({ - data: { - name: "Privée", - color: "#4F46E5", - description: `Calendrier synchronisé avec ${account.display_name || account.email}`, - userId: session?.user?.id || '', - } - }); - - // Update sync to point to new calendar - await prisma.calendarSync.update({ - where: { id: existingSync.id }, - data: { - calendarId: calendar.id, - syncEnabled: true - } - }); - continue; - } - - if (!existingSync.syncEnabled) { - const isAuthError = existingSync.lastSyncError?.includes('401') || - existingSync.lastSyncError?.includes('Unauthorized') || - existingSync.lastSyncError?.includes('invalid'); - - if (!isAuthError) { - // Only re-enable if it's not an authentication error - console.log(`[AGENDA] Re-enabling sync ${existingSync.id} for Infomaniak account ${account.email}`); - await prisma.calendarSync.update({ - where: { id: existingSync.id }, - data: { syncEnabled: true } - }); - } else { - // Try to discover calendars to verify if credentials are now valid - // But if discovery fails and we have an existing URL, re-enable sync anyway - // The existing URL might still work even if discovery fails - try { - const { discoverInfomaniakCalendars } = await import('@/lib/services/caldav-sync'); - const externalCalendars = await discoverInfomaniakCalendars( - account.email, - account.password! - ); - - if (externalCalendars.length > 0) { - // Credentials are now valid, re-enable sync with discovered calendar - await prisma.calendarSync.update({ - where: { id: existingSync.id }, - data: { - syncEnabled: true, - lastSyncError: null, - externalCalendarId: externalCalendars[0].id, - externalCalendarUrl: externalCalendars[0].url - } - }); - } else if (existingSync.externalCalendarUrl) { - // Discovery succeeded but no calendars found, but we have an existing URL - // Re-enable sync with existing URL - it might still work - await prisma.calendarSync.update({ - where: { id: existingSync.id }, - data: { - syncEnabled: true, - lastSyncError: 'Aucun calendrier trouvé lors de la découverte, utilisation de l\'URL existante' - } - }); - } - } catch (error) { - // Discovery failed, but if we have an existing URL, re-enable sync anyway - // The existing URL might still work even if discovery fails (e.g., due to network issues) - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - - if (existingSync.externalCalendarUrl) { - // We have an existing URL, re-enable sync - it worked before, might still work - await prisma.calendarSync.update({ - where: { id: existingSync.id }, - data: { - syncEnabled: true, - lastSyncError: `Découverte CalDAV échouée (${errorMessage}), mais réactivation du sync avec l'URL existante` - } - }); - } else { - // No existing URL, keep sync disabled - await prisma.calendarSync.update({ - where: { id: existingSync.id }, - data: { - lastSyncError: `Identifiants invalides ou expirés (${errorMessage}). Veuillez vérifier vos identifiants Infomaniak dans la page courrier.` - } - }); - } - } - } - } - continue; // Skip to next account - } - - if (!existingSync) { - // No sync exists for this account - try to discover and create calendar - // Only create calendar if discovery succeeds - try { - const { discoverInfomaniakCalendars } = await import('@/lib/services/caldav-sync'); - const externalCalendars = await discoverInfomaniakCalendars( - account.email, - account.password! - ); - - if (externalCalendars.length > 0) { - // Use the first calendar (usually the main calendar) - const mainCalendar = externalCalendars[0]; - - console.log(`[AGENDA] Creating Infomaniak calendar for ${account.email} with URL: ${mainCalendar.url}`); - - // Create a private calendar for this account - const calendar = await prisma.calendar.create({ - data: { - name: "Privée", - color: "#4F46E5", - description: `Calendrier synchronisé avec ${account.display_name || account.email}`, - userId: session?.user?.id || '', - } - }); - - // Create sync configuration - const syncConfig = await prisma.calendarSync.create({ - data: { - calendarId: calendar.id, - mailCredentialId: account.id, - provider: 'infomaniak', - externalCalendarId: mainCalendar.id, - externalCalendarUrl: mainCalendar.url, - syncEnabled: true, - syncFrequency: 15 - } - }); - - console.log(`[AGENDA] Created Infomaniak calendar sync: ${syncConfig.id} for calendar: ${calendar.id}`); - - // Trigger initial sync - try { - const { syncInfomaniakCalendar } = await import('@/lib/services/caldav-sync'); - await syncInfomaniakCalendar(syncConfig.id, true); - console.log(`[AGENDA] Initial sync completed for Infomaniak calendar: ${calendar.id}`); - } catch (syncError) { - const syncErrorMessage = syncError instanceof Error ? syncError.message : 'Unknown error'; - console.log(`[AGENDA] Initial sync failed for Infomaniak calendar: ${calendar.id} - ${syncErrorMessage}`); - await prisma.calendarSync.update({ - where: { id: syncConfig.id }, - data: { - lastSyncError: `Erreur de synchronisation: ${syncErrorMessage}` - } - }); - } - } - } catch (error) { - // Discovery failed - don't create calendar - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - console.log(`[AGENDA] Infomaniak calendar discovery failed for ${account.email} - ${errorMessage}. Calendar will not be created.`); - // Continue with other accounts even if one fails - } - } - } - } - // For each Microsoft account, ensure there's a synced calendar for (const account of microsoftAccounts) { // Check if a calendar sync already exists for this account (enabled or disabled) @@ -624,76 +310,6 @@ export default async function CalendarPage() { } } - // Auto-sync Infomaniak calendars if needed (background, don't block page load) - // Reload sync configs after URL corrections to get updated URLs - const infomaniakSyncConfigs = await prisma.calendarSync.findMany({ - where: { - provider: 'infomaniak', - syncEnabled: true, - calendar: { - userId: session?.user?.id || '' - } - } - }); - - console.log(`[AGENDA] Found ${infomaniakSyncConfigs.length} Infomaniak sync configs`); - - // Trigger sync for Infomaniak calendars that need it (async, don't wait) - for (const syncConfig of infomaniakSyncConfigs) { - // Skip sync if URL is still invalid (should have been fixed above, but double-check) - if (syncConfig.externalCalendarUrl === '/principals' || !syncConfig.externalCalendarUrl || syncConfig.externalCalendarUrl === '/') { - console.log(`[AGENDA] Skipping Infomaniak sync ${syncConfig.id} - invalid calendar URL: ${syncConfig.externalCalendarUrl}`); - continue; - } - - const minutesSinceLastSync = syncConfig.lastSyncAt - ? (Date.now() - syncConfig.lastSyncAt.getTime()) / (1000 * 60) - : Infinity; - - // Sync if never synced, or if enough time has passed (based on syncFrequency) - const needsSync = !syncConfig.lastSyncAt || - minutesSinceLastSync >= syncConfig.syncFrequency; - - console.log(`[AGENDA] Infomaniak sync config ${syncConfig.id}: lastSyncAt=${syncConfig.lastSyncAt}, minutesSinceLastSync=${minutesSinceLastSync.toFixed(1)}, needsSync=${needsSync}, syncFrequency=${syncConfig.syncFrequency}, calendarUrl=${syncConfig.externalCalendarUrl}`); - - if (needsSync) { - console.log(`[AGENDA] Triggering background sync for Infomaniak calendar ${syncConfig.id}`); - // Trigger sync in background (don't await to avoid blocking page load) - // The sync will update the database, and the next page load will show the events - // Use forceSync=true because we've already checked that sync is needed - import('@/lib/services/caldav-sync').then(({ syncInfomaniakCalendar }) => { - syncInfomaniakCalendar(syncConfig.id, true).then((result) => { - console.log(`[AGENDA] Infomaniak sync completed:`, { - calendarSyncId: syncConfig.id, - calendarId: syncConfig.calendarId, - synced: result.synced, - created: result.created, - updated: result.updated, - deleted: result.deleted, - }); - - // Verify events were created by checking the database - prisma.event.count({ - where: { calendarId: syncConfig.calendarId } - }).then((count) => { - console.log(`[AGENDA] Total events in calendar ${syncConfig.calendarId} after sync: ${count}`); - }).catch((err) => { - console.error('[AGENDA] Error counting events:', err); - }); - }).catch((error) => { - console.error('[AGENDA] Background sync failed for Infomaniak calendar', { - calendarSyncId: syncConfig.id, - calendarId: syncConfig.calendarId, - error: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined, - }); - }); - }); - } else { - console.log(`[AGENDA] Infomaniak sync skipped - too soon since last sync (${minutesSinceLastSync.toFixed(1)} min < ${syncConfig.syncFrequency} min)`); - } - } - // Auto-sync Microsoft calendars if needed (background, don't block page load) const microsoftSyncConfigs = await prisma.calendarSync.findMany({ where: { @@ -810,79 +426,18 @@ export default async function CalendarPage() { // No default calendar creation - only synced calendars from courrier - // Debug: Verify Infomaniak calendars exist in database - // Check both by provider and by mailCredential email - const allInfomaniakSyncs = await prisma.calendarSync.findMany({ - where: { - provider: 'infomaniak', - calendar: { - userId: session?.user?.id || '' - } - }, - include: { - calendar: true, - mailCredential: true - } - }); - - // Also check for syncs linked to Infomaniak accounts by email (in case mailCredentialId changed) - const infomaniakEmails = infomaniakAccounts.map(acc => acc.email); - const syncsByEmail = await prisma.calendarSync.findMany({ - where: { - provider: 'infomaniak', - calendar: { - userId: session?.user?.id || '' - }, - mailCredential: { - email: { - in: infomaniakEmails - } - } - }, - include: { - calendar: true, - mailCredential: true - } - }); - - console.log(`[AGENDA] Found ${allInfomaniakSyncs.length} Infomaniak syncs by provider, ${syncsByEmail.length} by email`); - [...allInfomaniakSyncs, ...syncsByEmail].forEach(sync => { - console.log(`[AGENDA] Infomaniak sync: id=${sync.id}, calendarId=${sync.calendarId}, calendarName=${sync.calendar?.name}, syncEnabled=${sync.syncEnabled}, mailCredentialId=${sync.mailCredentialId}, email=${sync.mailCredential?.email || 'none'}, hasMailCredential=${!!sync.mailCredential}`); - }); - - // Debug: Log calendars before filtering - console.log(`[AGENDA] Calendars before filtering: ${calendars.length}`); - const infomaniakBeforeFilter = calendars.filter(cal => cal.syncConfig?.provider === 'infomaniak'); - console.log(`[AGENDA] Infomaniak calendars before filtering: ${infomaniakBeforeFilter.length}`); - infomaniakBeforeFilter.forEach(cal => { - console.log(`[AGENDA] Before filter - Calendar: ${cal.name}, id: ${cal.id}, syncEnabled: ${cal.syncConfig?.syncEnabled}, hasMailCredential: ${!!cal.syncConfig?.mailCredential}`); - }); - // Filter out "Privée" and "Default" calendars that don't have active sync calendars = calendars.filter(cal => { const isPrivateOrDefault = cal.name === "Privée" || cal.name === "Default"; const hasActiveSync = cal.syncConfig?.syncEnabled === true && cal.syncConfig?.mailCredential; // Exclude "Privée"/"Default" calendars that are not actively synced - // Log for debugging if Infomaniak calendar is missing - if (isPrivateOrDefault && cal.syncConfig?.provider === 'infomaniak') { - if (!hasActiveSync) { - console.log(`[AGENDA] Infomaniak calendar found but sync is disabled: ${cal.id}, syncEnabled: ${cal.syncConfig?.syncEnabled}, hasMailCredential: ${!!cal.syncConfig?.mailCredential}, error: ${cal.syncConfig?.lastSyncError || 'none'}`); - } else { - console.log(`[AGENDA] Infomaniak calendar is active and will be displayed: ${cal.id}, email: ${cal.syncConfig?.mailCredential?.email}`); - } - } - if (isPrivateOrDefault && !hasActiveSync) { return false; } return true; }); - // Log final count of Infomaniak calendars - const infomaniakCalendars = calendars.filter(cal => cal.syncConfig?.provider === 'infomaniak'); - console.log(`[AGENDA] Final Infomaniak calendars count: ${infomaniakCalendars.length}`); - // Debug: Log all calendars with syncConfig to see what we have const calendarsWithSync = calendars.filter(cal => cal.syncConfig); console.log(`[AGENDA] Total calendars with syncConfig: ${calendarsWithSync.length}`);