Agenda refactor

This commit is contained in:
alma 2026-01-15 14:04:38 +01:00
parent 23e2e6c058
commit c96090c51c
2 changed files with 153 additions and 16 deletions

View File

@ -140,30 +140,23 @@ export async function fetchMicrosoftEvents(
};
// Add date filter if provided
// Microsoft Graph API supports filtering by start/dateTime for timed events
// For all-day events, we need to filter by start/date (date only, no time)
// We'll use a combined filter that handles both cases
// Note: Microsoft Graph API always uses dateTime for filtering, even for all-day events
// All-day events have dateTime set to midnight (00:00:00) in the event's timezone
// We filter by dateTime range which will include both timed and all-day events
if (startDate && endDate) {
// Format dates for Microsoft Graph API
// For dateTime filter: use ISO 8601 format
// Use ISO 8601 format for dateTime filter
const startDateTimeStr = startDate.toISOString();
const endDateTimeStr = endDate.toISOString();
// For date filter (all-day events): use YYYY-MM-DD format
const startDateStr = startDate.toISOString().split('T')[0];
const endDateStr = endDate.toISOString().split('T')[0];
// Combined filter: (start/dateTime >= startDate OR start/date >= startDate)
// AND (start/dateTime <= endDate OR start/date <= endDate)
// This handles both timed and all-day events
params.$filter = `(start/dateTime ge '${startDateTimeStr}' or start/date ge '${startDateStr}') and (start/dateTime le '${endDateTimeStr}' or start/date le '${endDateStr}')`;
// Microsoft Graph API filter: filter by start/dateTime
// This works for both timed events and all-day events (which have dateTime at midnight)
params.$filter = `start/dateTime ge '${startDateTimeStr}' and start/dateTime le '${endDateTimeStr}'`;
logger.debug('Microsoft Graph API filter', {
filter: params.$filter,
startDateTime: startDateTimeStr,
endDateTime: endDateTimeStr,
startDate: startDateStr,
endDate: endDateStr,
});
} else {
// If no date filter, get all events (might be too many, but useful for debugging)
@ -194,13 +187,35 @@ export async function fetchMicrosoftEvents(
});
const events = response.data.value || [];
// Log all event subjects to help debug missing events like "test" and "retest"
const allSubjects = events.map((e: MicrosoftEvent) => e.subject || '(sans titre)');
const testEvents = events.filter((e: MicrosoftEvent) =>
e.subject && (e.subject.toLowerCase().includes('test') || e.subject.toLowerCase().includes('retest'))
);
logger.info('Microsoft Graph API response', {
calendarId,
eventCount: events.length,
hasValue: !!response.data.value,
status: response.status,
// Log first few event IDs to verify they're being returned
eventIds: events.slice(0, 5).map((e: MicrosoftEvent) => e.id),
// Log first few event IDs and subjects to verify they're being returned
eventIds: events.slice(0, 10).map((e: MicrosoftEvent) => ({
id: e.id,
subject: e.subject || '(sans titre)',
start: e.start.dateTime || e.start.date,
isAllDay: e.isAllDay,
})),
// Log all event subjects to help debug missing events
allSubjects,
// Specifically check for test/retest events
testEventsFound: testEvents.length,
testEventDetails: testEvents.map((e: MicrosoftEvent) => ({
id: e.id,
subject: e.subject,
start: e.start.dateTime || e.start.date,
isAllDay: e.isAllDay,
})),
});
// Log if we got fewer events than expected

View File

@ -0,0 +1,122 @@
#!/usr/bin/env ts-node
/**
* Script to clean up duplicate calendars created from failed Microsoft account installations
* Removes calendars that have no events and no active sync config
*/
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function cleanupDuplicateCalendars() {
try {
console.log('🔍 Searching for duplicate calendars...\n');
// Find all calendars named "Privée" with Microsoft sync
const privateCalendars = await prisma.calendar.findMany({
where: {
name: 'Privée',
},
include: {
syncConfig: {
include: {
mailCredential: true,
},
},
_count: {
select: {
events: true,
},
},
},
});
console.log(`Found ${privateCalendars.length} calendars named "Privée"\n`);
// Group by email (from mailCredential)
const calendarsByEmail = new Map<string, typeof privateCalendars>();
for (const calendar of privateCalendars) {
const email = calendar.syncConfig?.mailCredential?.email || 'unknown';
if (!calendarsByEmail.has(email)) {
calendarsByEmail.set(email, []);
}
calendarsByEmail.get(email)!.push(calendar);
}
let totalDeleted = 0;
let totalKept = 0;
for (const [email, calendars] of calendarsByEmail.entries()) {
if (calendars.length <= 1) {
continue; // No duplicates for this email
}
console.log(`\n📧 Processing email: ${email} (${calendars.length} calendars)`);
// Sort calendars by:
// 1. Has events (prefer calendars with events)
// 2. Has active sync (prefer calendars with active sync)
// 3. Created date (prefer newest)
calendars.sort((a, b) => {
// First: prefer calendars with events
if (a._count.events !== b._count.events) {
return b._count.events - a._count.events;
}
// Second: prefer calendars with active sync
const aHasActiveSync = a.syncConfig?.syncEnabled === true;
const bHasActiveSync = b.syncConfig?.syncEnabled === true;
if (aHasActiveSync !== bHasActiveSync) {
return bHasActiveSync ? 1 : -1;
}
// Third: prefer newest
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
});
const toKeep = calendars[0];
const toDelete = calendars.slice(1);
console.log(` ✅ Keeping: ${toKeep.id}`);
console.log(` Events: ${toKeep._count.events}`);
console.log(` Sync enabled: ${toKeep.syncConfig?.syncEnabled || false}`);
console.log(` Created: ${toKeep.createdAt.toISOString()}`);
for (const calendar of toDelete) {
console.log(` 🗑️ Deleting: ${calendar.id}`);
console.log(` Events: ${calendar._count.events}`);
console.log(` Sync enabled: ${calendar.syncConfig?.syncEnabled || false}`);
console.log(` Created: ${calendar.createdAt.toISOString()}`);
// Delete sync config first (if exists)
if (calendar.syncConfig) {
await prisma.calendarSync.delete({
where: { id: calendar.syncConfig.id },
});
}
// Delete calendar (events will be cascade deleted)
await prisma.calendar.delete({
where: { id: calendar.id },
});
totalDeleted++;
}
totalKept++;
}
console.log(`\n✅ Cleanup completed!`);
console.log(` Kept: ${totalKept} calendars`);
console.log(` Deleted: ${totalDeleted} duplicate calendars`);
} catch (error) {
console.error('❌ Error cleaning up duplicate calendars:', error);
throw error;
} finally {
await prisma.$disconnect();
}
}
cleanupDuplicateCalendars();