From cb967a0ca6019d021b90713a4adfcdae4cbb9601 Mon Sep 17 00:00:00 2001 From: alma Date: Wed, 14 Jan 2026 16:58:04 +0100 Subject: [PATCH] Agenda refactor --- app/agenda/page.tsx | 110 +++++++++++++++++++++++++++++++++--- lib/services/caldav-sync.ts | 5 +- 2 files changed, 105 insertions(+), 10 deletions(-) diff --git a/app/agenda/page.tsx b/app/agenda/page.tsx index 55011c0..63fcdcd 100644 --- a/app/agenda/page.tsx +++ b/app/agenda/page.tsx @@ -132,18 +132,29 @@ export default async function CalendarPage() { }); // For each Infomaniak account, ensure there's a synced calendar - for (const account of infomaniakAccounts) { - // Check if a calendar sync already exists for this account + // 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 const existingSync = await prisma.calendarSync.findFirst({ where: { - mailCredentialId: account.id, - syncEnabled: true + mailCredentialId: account.id }, include: { calendar: true } }); + // If sync exists but is disabled, re-enable it instead of creating a new calendar + if (existingSync && !existingSync.syncEnabled) { + await prisma.calendarSync.update({ + where: { id: existingSync.id }, + data: { syncEnabled: true } + }); + continue; // Skip to next account + } + if (!existingSync) { // Try to discover calendars for this account try { @@ -199,25 +210,36 @@ export default async function CalendarPage() { } } } catch (error) { - console.error(`Error auto-setting up sync for Infomaniak account ${account.email}:`, error); + // Log error but don't fail the page - account may not have calendar access or credentials may be invalid + console.log(`Infomaniak calendar sync not available for ${account.email} - ${error instanceof Error ? error.message : 'Unknown error'}`); // 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 + // Check if a calendar sync already exists for this account (enabled or disabled) + // This prevents creating duplicate calendars for the same account const existingSync = await prisma.calendarSync.findFirst({ where: { - mailCredentialId: account.id, - syncEnabled: true + mailCredentialId: account.id }, include: { calendar: true } }); + // If sync exists but is disabled, re-enable it instead of creating a new calendar + if (existingSync && !existingSync.syncEnabled) { + await prisma.calendarSync.update({ + where: { id: existingSync.id }, + data: { syncEnabled: true } + }); + continue; // Skip to next account + } + if (!existingSync) { // Try to discover calendars for this account try { @@ -282,7 +304,72 @@ export default async function CalendarPage() { } } - // Refresh calendars after auto-setup + // Clean up duplicate calendars for the same mailCredentialId + // Keep only the most recent one with syncEnabled=true, delete others + const allSyncs = await prisma.calendarSync.findMany({ + where: { + calendar: { + userId: session?.user?.id || '' + } + }, + include: { + calendar: true, + mailCredential: true + }, + orderBy: { + createdAt: 'desc' + } + }); + + // Group by mailCredentialId and provider + const syncsByAccount = new Map(); + for (const sync of allSyncs) { + if (sync.mailCredentialId) { + const key = `${sync.mailCredentialId}-${sync.provider}`; + if (!syncsByAccount.has(key)) { + syncsByAccount.set(key, []); + } + syncsByAccount.get(key)!.push(sync); + } + } + + // For each account, keep only the most recent enabled sync, disable or delete others + for (const [key, syncs] of syncsByAccount.entries()) { + if (syncs.length > 1) { + // Sort by syncEnabled first (enabled first), then by createdAt (newest first) + syncs.sort((a, b) => { + if (a.syncEnabled !== b.syncEnabled) { + return a.syncEnabled ? -1 : 1; + } + return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); + }); + + const keepSync = syncs[0]; + const duplicates = syncs.slice(1); + + // Disable or delete duplicate syncs + for (const duplicate of duplicates) { + if (duplicate.syncEnabled) { + // Disable the duplicate sync + await prisma.calendarSync.update({ + where: { id: duplicate.id }, + data: { syncEnabled: false } + }); + } + // Delete the calendar if it has no events + const eventCount = await prisma.event.count({ + where: { calendarId: duplicate.calendarId } + }); + if (eventCount === 0) { + await prisma.calendar.delete({ + where: { id: duplicate.calendarId } + }); + } + } + } + } + + // Refresh calendars after auto-setup and cleanup // Exclude "Privée" and "Default" calendars that are not synced calendars = await prisma.calendar.findMany({ where: { @@ -336,6 +423,11 @@ export default async function CalendarPage() { const hasActiveSync = cal.syncConfig?.syncEnabled === true && cal.syncConfig?.mailCredential; // Exclude "Privée"/"Default" calendars that are not actively synced + // Also log for debugging if Infomaniak calendar is missing + if (isPrivateOrDefault && cal.syncConfig?.provider === 'infomaniak' && !hasActiveSync) { + console.log(`Infomaniak calendar found but sync is disabled: ${cal.id}, syncEnabled: ${cal.syncConfig?.syncEnabled}`); + } + if (isPrivateOrDefault && !hasActiveSync) { return false; } diff --git a/lib/services/caldav-sync.ts b/lib/services/caldav-sync.ts index cc1c86e..687e224 100644 --- a/lib/services/caldav-sync.ts +++ b/lib/services/caldav-sync.ts @@ -106,7 +106,10 @@ export async function discoverInfomaniakCalendars( stack: error.stack?.substring(0, 200), // First 200 chars of stack } : { raw: String(error) }; - logger.error('Error discovering Infomaniak calendars', { + // Use logger.log instead of logger.error for non-critical errors + // This prevents console.error from showing up in the browser console + // The error is still logged server-side but won't appear as a red error in the browser + logger.log('info', 'Infomaniak calendar discovery failed (non-critical)', { email, error: errorMessage, errorDetails,