Agenda refactor
This commit is contained in:
parent
5d90bc6989
commit
5a4e746bf4
@ -131,6 +131,50 @@ 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)
|
||||||
|
]);
|
||||||
|
|
||||||
|
const orphanedSyncs = await prisma.calendarSync.findMany({
|
||||||
|
where: {
|
||||||
|
calendar: {
|
||||||
|
userId: session?.user?.id || ''
|
||||||
|
},
|
||||||
|
mailCredentialId: {
|
||||||
|
not: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
calendar: true,
|
||||||
|
mailCredential: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete syncs where mailCredential no longer exists
|
||||||
|
for (const sync of orphanedSyncs) {
|
||||||
|
if (sync.mailCredentialId && !allMailCredentialIds.has(sync.mailCredentialId)) {
|
||||||
|
console.log(`[AGENDA] Deleting orphaned sync for non-existent mailCredentialId: ${sync.mailCredentialId}`);
|
||||||
|
// Delete the calendar if it has no events
|
||||||
|
const eventCount = await prisma.event.count({
|
||||||
|
where: { calendarId: sync.calendarId }
|
||||||
|
});
|
||||||
|
if (eventCount === 0) {
|
||||||
|
await prisma.calendar.delete({
|
||||||
|
where: { id: sync.calendarId }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Just disable the sync, keep the calendar
|
||||||
|
await prisma.calendarSync.update({
|
||||||
|
where: { id: sync.id },
|
||||||
|
data: { syncEnabled: false }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// For each Infomaniak account, ensure there's a synced calendar
|
// For each Infomaniak account, ensure there's a synced calendar
|
||||||
// Skip if no Infomaniak accounts exist (user may only have Microsoft accounts)
|
// Skip if no Infomaniak accounts exist (user may only have Microsoft accounts)
|
||||||
if (infomaniakAccounts.length > 0) {
|
if (infomaniakAccounts.length > 0) {
|
||||||
@ -148,18 +192,46 @@ export default async function CalendarPage() {
|
|||||||
|
|
||||||
// If sync exists but is disabled, check if it's due to invalid credentials
|
// 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)
|
// Don't re-enable if the last error was 401 (invalid credentials)
|
||||||
if (existingSync && !existingSync.syncEnabled) {
|
if (existingSync) {
|
||||||
const isAuthError = existingSync.lastSyncError?.includes('401') ||
|
console.log(`[AGENDA] Found existing sync for Infomaniak account ${account.email}: syncId=${existingSync.id}, calendarId=${existingSync.calendarId}, syncEnabled=${existingSync.syncEnabled}, hasCalendar=${!!existingSync.calendar}`);
|
||||||
existingSync.lastSyncError?.includes('Unauthorized') ||
|
|
||||||
existingSync.lastSyncError?.includes('invalid');
|
|
||||||
|
|
||||||
if (!isAuthError) {
|
// Check if calendar still exists
|
||||||
// Only re-enable if it's not an authentication error
|
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({
|
await prisma.calendarSync.update({
|
||||||
where: { id: existingSync.id },
|
where: { id: existingSync.id },
|
||||||
data: { syncEnabled: true }
|
data: {
|
||||||
|
calendarId: calendar.id,
|
||||||
|
syncEnabled: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
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
|
// 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
|
// But if discovery fails and we have an existing URL, re-enable sync anyway
|
||||||
// The existing URL might still work even if discovery fails
|
// The existing URL might still work even if discovery fails
|
||||||
@ -221,18 +293,23 @@ export default async function CalendarPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!existingSync) {
|
if (!existingSync) {
|
||||||
// Try to discover calendars for this account
|
// No sync exists for this account - try to discover and create calendar
|
||||||
|
// Only create calendar if discovery succeeds
|
||||||
try {
|
try {
|
||||||
const { discoverInfomaniakCalendars } = await import('@/lib/services/caldav-sync');
|
const { discoverInfomaniakCalendars } = await import('@/lib/services/caldav-sync');
|
||||||
const externalCalendars = await discoverInfomaniakCalendars(
|
const externalCalendars = await discoverInfomaniakCalendars(
|
||||||
account.email,
|
account.email,
|
||||||
account.password!
|
account.password!
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(`[AGENDA] Discovered ${externalCalendars.length} calendars for Infomaniak account ${account.email}`);
|
||||||
|
|
||||||
if (externalCalendars.length > 0) {
|
if (externalCalendars.length > 0) {
|
||||||
// Use the first calendar (usually the main calendar)
|
// Use the first calendar (usually the main calendar)
|
||||||
const mainCalendar = externalCalendars[0];
|
const mainCalendar = externalCalendars[0];
|
||||||
|
|
||||||
|
console.log(`[AGENDA] Creating Infomaniak calendar for ${account.email} with URL: ${mainCalendar.url}`);
|
||||||
|
|
||||||
// Create a private calendar for this account
|
// Create a private calendar for this account
|
||||||
const calendar = await prisma.calendar.create({
|
const calendar = await prisma.calendar.create({
|
||||||
data: {
|
data: {
|
||||||
@ -244,7 +321,7 @@ export default async function CalendarPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Create sync configuration
|
// Create sync configuration
|
||||||
await prisma.calendarSync.create({
|
const syncConfig = await prisma.calendarSync.create({
|
||||||
data: {
|
data: {
|
||||||
calendarId: calendar.id,
|
calendarId: calendar.id,
|
||||||
mailCredentialId: account.id,
|
mailCredentialId: account.id,
|
||||||
@ -256,50 +333,28 @@ export default async function CalendarPage() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(`[AGENDA] Created Infomaniak calendar sync: ${syncConfig.id} for calendar: ${calendar.id}`);
|
||||||
|
|
||||||
// Trigger initial sync
|
// Trigger initial sync
|
||||||
try {
|
try {
|
||||||
const { syncInfomaniakCalendar } = await import('@/lib/services/caldav-sync');
|
const { syncInfomaniakCalendar } = await import('@/lib/services/caldav-sync');
|
||||||
const syncConfig = await prisma.calendarSync.findUnique({
|
await syncInfomaniakCalendar(syncConfig.id, true);
|
||||||
where: { calendarId: calendar.id },
|
console.log(`[AGENDA] Initial sync completed for Infomaniak calendar: ${calendar.id}`);
|
||||||
include: {
|
} catch (syncError) {
|
||||||
calendar: true,
|
const syncErrorMessage = syncError instanceof Error ? syncError.message : 'Unknown error';
|
||||||
mailCredential: true
|
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}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (syncConfig) {
|
|
||||||
await syncInfomaniakCalendar(syncConfig.id, true);
|
|
||||||
}
|
|
||||||
} catch (syncError) {
|
|
||||||
console.error('Error during initial sync:', syncError);
|
|
||||||
// Don't fail if sync fails, calendar is still created
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Log error but don't fail the page - account may not have calendar access or credentials may be invalid
|
// Discovery failed - don't create calendar
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||||
console.log(`Infomaniak calendar sync not available for ${account.email} - ${errorMessage}`);
|
console.log(`[AGENDA] Infomaniak calendar discovery failed for ${account.email} - ${errorMessage}. Calendar will not be created.`);
|
||||||
|
|
||||||
// If it's a 401 error, the credentials are likely invalid - update lastSyncError in existing sync if any
|
|
||||||
if (errorMessage.includes('401') || errorMessage.includes('Unauthorized')) {
|
|
||||||
// Check if there's a disabled sync for this account
|
|
||||||
const disabledSync = await prisma.calendarSync.findFirst({
|
|
||||||
where: {
|
|
||||||
mailCredentialId: account.id,
|
|
||||||
provider: 'infomaniak',
|
|
||||||
syncEnabled: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (disabledSync) {
|
|
||||||
// Update the error message
|
|
||||||
await prisma.calendarSync.update({
|
|
||||||
where: { id: disabledSync.id },
|
|
||||||
data: {
|
|
||||||
lastSyncError: `Identifiants invalides ou expirés (401 Unauthorized). Veuillez vérifier vos identifiants Infomaniak dans la page courrier.`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Continue with other accounts even if one fails
|
// Continue with other accounts even if one fails
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -392,12 +447,59 @@ export default async function CalendarPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up orphaned syncs (syncs with mailCredentialId that no longer exists)
|
||||||
|
// This can happen when a user deletes and re-adds an email account
|
||||||
|
const allMailCredentialIds = new Set([
|
||||||
|
...infomaniakAccounts.map(acc => acc.id),
|
||||||
|
...microsoftAccounts.map(acc => acc.id)
|
||||||
|
]);
|
||||||
|
|
||||||
|
const orphanedSyncs = await prisma.calendarSync.findMany({
|
||||||
|
where: {
|
||||||
|
calendar: {
|
||||||
|
userId: session?.user?.id || ''
|
||||||
|
},
|
||||||
|
mailCredentialId: {
|
||||||
|
not: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
calendar: true,
|
||||||
|
mailCredential: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete syncs where mailCredential no longer exists
|
||||||
|
for (const sync of orphanedSyncs) {
|
||||||
|
if (sync.mailCredentialId && !allMailCredentialIds.has(sync.mailCredentialId)) {
|
||||||
|
console.log(`[AGENDA] Deleting orphaned sync for non-existent mailCredentialId: ${sync.mailCredentialId}`);
|
||||||
|
// Delete the calendar if it has no events
|
||||||
|
const eventCount = await prisma.event.count({
|
||||||
|
where: { calendarId: sync.calendarId }
|
||||||
|
});
|
||||||
|
if (eventCount === 0) {
|
||||||
|
await prisma.calendar.delete({
|
||||||
|
where: { id: sync.calendarId }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Just disable the sync, keep the calendar
|
||||||
|
await prisma.calendarSync.update({
|
||||||
|
where: { id: sync.id },
|
||||||
|
data: { syncEnabled: false }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up duplicate calendars for the same mailCredentialId
|
// Clean up duplicate calendars for the same mailCredentialId
|
||||||
// Keep only the most recent one with syncEnabled=true, delete others
|
// Keep only the most recent one with syncEnabled=true, delete others
|
||||||
const allSyncs = await prisma.calendarSync.findMany({
|
const allSyncs = await prisma.calendarSync.findMany({
|
||||||
where: {
|
where: {
|
||||||
calendar: {
|
calendar: {
|
||||||
userId: session?.user?.id || ''
|
userId: session?.user?.id || ''
|
||||||
|
},
|
||||||
|
mailCredentialId: {
|
||||||
|
in: Array.from(allMailCredentialIds)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
@ -507,6 +609,24 @@ export default async function CalendarPage() {
|
|||||||
|
|
||||||
// No default calendar creation - only synced calendars from courrier
|
// No default calendar creation - only synced calendars from courrier
|
||||||
|
|
||||||
|
// Debug: Verify Infomaniak calendars exist in database
|
||||||
|
const allInfomaniakSyncs = await prisma.calendarSync.findMany({
|
||||||
|
where: {
|
||||||
|
provider: 'infomaniak',
|
||||||
|
calendar: {
|
||||||
|
userId: session?.user?.id || ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
calendar: true,
|
||||||
|
mailCredential: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(`[AGENDA] Found ${allInfomaniakSyncs.length} Infomaniak syncs in database`);
|
||||||
|
allInfomaniakSyncs.forEach(sync => {
|
||||||
|
console.log(`[AGENDA] Infomaniak sync: id=${sync.id}, calendarId=${sync.calendarId}, calendarName=${sync.calendar?.name}, syncEnabled=${sync.syncEnabled}, mailCredentialId=${sync.mailCredentialId}, hasMailCredential=${!!sync.mailCredential}`);
|
||||||
|
});
|
||||||
|
|
||||||
// Debug: Log calendars before filtering
|
// Debug: Log calendars before filtering
|
||||||
console.log(`[AGENDA] Calendars before filtering: ${calendars.length}`);
|
console.log(`[AGENDA] Calendars before filtering: ${calendars.length}`);
|
||||||
const infomaniakBeforeFilter = calendars.filter(cal => cal.syncConfig?.provider === 'infomaniak');
|
const infomaniakBeforeFilter = calendars.filter(cal => cal.syncConfig?.provider === 'infomaniak');
|
||||||
|
|||||||
@ -48,8 +48,40 @@ export async function discoverInfomaniakCalendars(
|
|||||||
try {
|
try {
|
||||||
const client = await getInfomaniakCalDAVClient(email, password);
|
const client = await getInfomaniakCalDAVClient(email, password);
|
||||||
|
|
||||||
// List all calendars using PROPFIND on /caldav path
|
// List all calendars using PROPFIND
|
||||||
const items = await client.getDirectoryContents('/caldav');
|
// Try different paths: root, /caldav, /calendars/{username}
|
||||||
|
let items;
|
||||||
|
let triedPaths: string[] = [];
|
||||||
|
|
||||||
|
// Try root path first
|
||||||
|
try {
|
||||||
|
logger.debug('Trying CalDAV discovery on root path /');
|
||||||
|
items = await client.getDirectoryContents('/');
|
||||||
|
logger.debug(`CalDAV discovery succeeded on root path, found ${items.length} items`);
|
||||||
|
} catch (rootError) {
|
||||||
|
triedPaths.push('/');
|
||||||
|
logger.debug('Root path failed, trying /caldav path');
|
||||||
|
|
||||||
|
// Try /caldav path
|
||||||
|
try {
|
||||||
|
items = await client.getDirectoryContents('/caldav');
|
||||||
|
logger.debug(`CalDAV discovery succeeded on /caldav path, found ${items.length} items`);
|
||||||
|
} catch (caldavError) {
|
||||||
|
triedPaths.push('/caldav');
|
||||||
|
|
||||||
|
// Try /calendars/{username} path
|
||||||
|
const username = email.split('@')[0];
|
||||||
|
const calendarsPath = `/calendars/${username}`;
|
||||||
|
logger.debug(`Trying CalDAV discovery on ${calendarsPath} path`);
|
||||||
|
try {
|
||||||
|
items = await client.getDirectoryContents(calendarsPath);
|
||||||
|
logger.debug(`CalDAV discovery succeeded on ${calendarsPath} path, found ${items.length} items`);
|
||||||
|
} catch (calendarsError) {
|
||||||
|
triedPaths.push(calendarsPath);
|
||||||
|
throw new Error(`CalDAV discovery failed on all paths (${triedPaths.join(', ')}). Last error: ${calendarsError instanceof Error ? calendarsError.message : String(calendarsError)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const calendars: CalDAVCalendar[] = [];
|
const calendars: CalDAVCalendar[] = [];
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user