123 lines
3.7 KiB
TypeScript
123 lines
3.7 KiB
TypeScript
#!/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();
|