#!/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(); 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();