Agenda refactor

This commit is contained in:
alma 2026-01-15 12:52:50 +01:00
parent 946a110054
commit fd24dfbd21
2 changed files with 96 additions and 34 deletions

View File

@ -242,14 +242,20 @@ export default async function CalendarPage() {
console.log(`[AGENDA] Invalid calendar URL detected (${existingSync.externalCalendarUrl}), attempting to rediscover...`); console.log(`[AGENDA] Invalid calendar URL detected (${existingSync.externalCalendarUrl}), attempting to rediscover...`);
try { try {
const { discoverInfomaniakCalendars } = await import('@/lib/services/caldav-sync'); 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( const externalCalendars = await discoverInfomaniakCalendars(
account.email, account.email,
account.password! account.password
); );
console.log(`[AGENDA] Rediscovery result: found ${externalCalendars.length} calendars for ${account.email}`);
if (externalCalendars.length > 0) { if (externalCalendars.length > 0) {
const mainCalendar = externalCalendars[0]; const mainCalendar = externalCalendars[0];
console.log(`[AGENDA] Updating sync with correct calendar URL: ${mainCalendar.url}`); console.log(`[AGENDA] Updating sync with correct calendar URL: ${mainCalendar.url} (name: ${mainCalendar.name})`);
await prisma.calendarSync.update({ await prisma.calendarSync.update({
where: { id: existingSync.id }, where: { id: existingSync.id },
data: { data: {
@ -265,12 +271,36 @@ export default async function CalendarPage() {
}); });
if (updatedSync) { if (updatedSync) {
existingSync = updatedSync; existingSync = updatedSync;
console.log(`[AGENDA] Sync config updated successfully, new URL: ${updatedSync.externalCalendarUrl}`);
} }
} else { } else {
console.log(`[AGENDA] No calendars found during rediscovery for ${account.email}`); 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) { });
console.error(`[AGENDA] Error rediscovering calendars:`, rediscoverError); }
}
} 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
});
} }
} }

View File

@ -48,18 +48,31 @@ export async function discoverInfomaniakCalendars(
const client = await getInfomaniakCalDAVClient(email, password); const client = await getInfomaniakCalDAVClient(email, password);
// List all calendars using PROPFIND on root // List all calendars using PROPFIND on root
logger.debug('Discovering Infomaniak calendars', { email });
const items = await client.getDirectoryContents('/'); const items = await client.getDirectoryContents('/');
logger.debug('Found items in root directory', {
email,
itemsCount: items.length,
items: items.map(item => ({ filename: item.filename, type: item.type })),
});
const calendars: CalDAVCalendar[] = []; const calendars: CalDAVCalendar[] = [];
for (const item of items) { for (const item of items) {
// Skip non-directories, root, and special directories like /principals // Skip non-directories, root, and special directories like /principals
if (item.type !== 'directory' || item.filename === '/' || item.filename === '/principals') { if (item.type !== 'directory' || item.filename === '/' || item.filename === '/principals') {
logger.debug('Skipping item', {
filename: item.filename,
type: item.type,
reason: item.type !== 'directory' ? 'not a directory' : 'special directory',
});
continue; continue;
} }
// Get calendar properties to verify it's actually a calendar // Get calendar properties to verify it's actually a calendar
try { try {
logger.debug('Checking if item is a calendar', { filename: item.filename });
const props = await client.customRequest(item.filename, { const props = await client.customRequest(item.filename, {
method: 'PROPFIND', method: 'PROPFIND',
headers: { headers: {
@ -77,11 +90,23 @@ export async function discoverInfomaniakCalendars(
}); });
// Check if this is actually a calendar (has <c:calendar> in resourcetype) // Check if this is actually a calendar (has <c:calendar> in resourcetype)
const isCalendar = props.data && props.data.includes('<c:calendar'); // Try multiple patterns to be more flexible with XML namespaces
const dataStr = props.data || '';
const isCalendar = dataStr.includes('<c:calendar') ||
dataStr.includes('calendar') ||
dataStr.includes('urn:ietf:params:xml:ns:caldav');
logger.debug('Calendar check result', {
filename: item.filename,
isCalendar,
hasData: !!props.data,
dataPreview: props.data ? props.data.substring(0, 500) : 'no data',
});
if (!isCalendar) { if (!isCalendar) {
logger.debug('Skipping non-calendar directory', { logger.debug('Skipping non-calendar directory', {
filename: item.filename, filename: item.filename,
reason: 'resourcetype does not indicate calendar',
}); });
continue; continue;
} }
@ -90,12 +115,15 @@ export async function discoverInfomaniakCalendars(
const displayName = extractDisplayName(props.data); const displayName = extractDisplayName(props.data);
const color = extractCalendarColor(props.data); const color = extractCalendarColor(props.data);
calendars.push({ const calendar = {
id: item.filename.replace(/^\//, '').replace(/\/$/, ''), id: item.filename.replace(/^\//, '').replace(/\/$/, ''),
name: displayName || item.basename || 'Calendrier', name: displayName || item.basename || 'Calendrier',
url: item.filename, url: item.filename,
color: color, color: color,
}); };
logger.debug('Found valid calendar', calendar);
calendars.push(calendar);
} catch (error) { } catch (error) {
logger.error('Error fetching calendar properties', { logger.error('Error fetching calendar properties', {
calendar: item.filename, calendar: item.filename,
@ -105,19 +133,23 @@ export async function discoverInfomaniakCalendars(
} }
} }
logger.info('Infomaniak calendar discovery completed', {
email,
calendarsFound: calendars.length,
calendars: calendars.map(cal => ({ id: cal.id, name: cal.name, url: cal.url })),
});
return calendars; return calendars;
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error); const errorMessage = error instanceof Error ? error.message : String(error);
const errorDetails = error instanceof Error ? { const errorDetails = error instanceof Error ? {
name: error.name, name: error.name,
message: error.message, message: error.message,
stack: error.stack?.substring(0, 200), // First 200 chars of stack stack: error.stack?.substring(0, 500), // More stack for debugging
} : { raw: String(error) }; } : { raw: String(error) };
// Use logger.log instead of logger.error for non-critical errors // Log as error for debugging, but don't throw to avoid breaking the page
// This prevents console.error from showing up in the browser console logger.error('Infomaniak calendar discovery failed', {
// 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, email,
error: errorMessage, error: errorMessage,
errorDetails, errorDetails,