Agenda refactor
This commit is contained in:
parent
23e2e6c058
commit
c96090c51c
@ -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
|
||||
|
||||
122
scripts/cleanup-duplicate-calendars.ts
Normal file
122
scripts/cleanup-duplicate-calendars.ts
Normal 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();
|
||||
Loading…
Reference in New Issue
Block a user