197 lines
6.4 KiB
TypeScript
197 lines
6.4 KiB
TypeScript
#!/usr/bin/env ts-node
|
|
|
|
/**
|
|
* Script to consolidate duplicate "Privée" calendars
|
|
* - Identifies the calendar to keep (with active sync and most events)
|
|
* - Migrates events from duplicate calendars to the one to keep
|
|
* - Deletes duplicate calendars and their sync configs
|
|
*/
|
|
|
|
import { PrismaClient } from '@prisma/client';
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
async function consolidatePrivateCalendars() {
|
|
try {
|
|
console.log('🔍 Searching for duplicate "Privée" calendars...\n');
|
|
|
|
// Find all calendars named "Privée"
|
|
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`);
|
|
|
|
if (privateCalendars.length <= 1) {
|
|
console.log('✅ No duplicates found. Nothing to consolidate.\n');
|
|
return;
|
|
}
|
|
|
|
// Find the calendar to keep:
|
|
// 1. Has active sync (syncEnabled = true)
|
|
// 2. Has the most events
|
|
// 3. Most recent
|
|
const calendarsToSort = [...privateCalendars];
|
|
calendarsToSort.sort((a, b) => {
|
|
// Priority 1: Active sync
|
|
const aHasActiveSync = a.syncConfig?.syncEnabled === true;
|
|
const bHasActiveSync = b.syncConfig?.syncEnabled === true;
|
|
if (aHasActiveSync && !bHasActiveSync) return -1;
|
|
if (!aHasActiveSync && bHasActiveSync) return 1;
|
|
|
|
// Priority 2: Most events
|
|
const aEventCount = a._count.events;
|
|
const bEventCount = b._count.events;
|
|
if (aEventCount !== bEventCount) {
|
|
return bEventCount - aEventCount; // Descending
|
|
}
|
|
|
|
// Priority 3: Most recent
|
|
return b.createdAt.getTime() - a.createdAt.getTime(); // Descending
|
|
});
|
|
|
|
const calendarToKeep = calendarsToSort[0];
|
|
const calendarsToDelete = calendarsToSort.slice(1);
|
|
|
|
console.log('📋 Calendar to KEEP:');
|
|
console.log(` ID: ${calendarToKeep.id}`);
|
|
console.log(` Events: ${calendarToKeep._count.events}`);
|
|
console.log(` Sync enabled: ${calendarToKeep.syncConfig?.syncEnabled || false}`);
|
|
console.log(` Provider: ${calendarToKeep.syncConfig?.provider || 'none'}`);
|
|
console.log(` Created: ${calendarToKeep.createdAt}\n`);
|
|
|
|
console.log(`🗑️ Calendars to DELETE (${calendarsToDelete.length}):`);
|
|
for (const cal of calendarsToDelete) {
|
|
console.log(` ID: ${cal.id}`);
|
|
console.log(` Events: ${cal._count.events}`);
|
|
console.log(` Sync enabled: ${cal.syncConfig?.syncEnabled || false}`);
|
|
console.log(` Created: ${cal.createdAt}\n`);
|
|
}
|
|
|
|
// Migrate events from calendars to delete to the calendar to keep
|
|
let totalMigrated = 0;
|
|
for (const calToDelete of calendarsToDelete) {
|
|
const eventsToMigrate = await prisma.event.findMany({
|
|
where: {
|
|
calendarId: calToDelete.id,
|
|
},
|
|
});
|
|
|
|
console.log(`📦 Migrating ${eventsToMigrate.length} events from calendar ${calToDelete.id}...`);
|
|
|
|
for (const event of eventsToMigrate) {
|
|
// Check if event with same externalEventId already exists in target calendar
|
|
const externalId = (event as any).externalEventId;
|
|
|
|
if (externalId) {
|
|
// Check if event with this externalEventId already exists in target calendar
|
|
const existingEvent = await prisma.event.findFirst({
|
|
where: {
|
|
calendarId: calendarToKeep.id,
|
|
externalEventId: externalId,
|
|
},
|
|
});
|
|
|
|
if (existingEvent) {
|
|
console.log(` ⚠️ Skipping event "${event.title}" - already exists in target calendar (externalEventId: ${externalId.substring(0, 50)}...)`);
|
|
// Delete the duplicate event
|
|
await prisma.event.delete({
|
|
where: { id: event.id },
|
|
});
|
|
continue;
|
|
}
|
|
} else {
|
|
// For events without externalEventId, check by title + date
|
|
const existingEvent = await prisma.event.findFirst({
|
|
where: {
|
|
calendarId: calendarToKeep.id,
|
|
title: event.title,
|
|
start: event.start,
|
|
},
|
|
});
|
|
|
|
if (existingEvent) {
|
|
console.log(` ⚠️ Skipping event "${event.title}" - already exists in target calendar (title + date match)`);
|
|
// Delete the duplicate event
|
|
await prisma.event.delete({
|
|
where: { id: event.id },
|
|
});
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Migrate event to target calendar
|
|
try {
|
|
await prisma.event.update({
|
|
where: { id: event.id },
|
|
data: {
|
|
calendarId: calendarToKeep.id,
|
|
},
|
|
});
|
|
totalMigrated++;
|
|
console.log(` ✅ Migrated: "${event.title}"`);
|
|
} catch (error) {
|
|
console.error(` ❌ Error migrating event "${event.title}":`, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`\n✅ Migrated ${totalMigrated} events to calendar ${calendarToKeep.id}\n`);
|
|
|
|
// Delete duplicate calendars and their sync configs
|
|
for (const calToDelete of calendarsToDelete) {
|
|
console.log(`🗑️ Deleting calendar ${calToDelete.id}...`);
|
|
|
|
// Delete sync config if exists
|
|
if (calToDelete.syncConfig) {
|
|
await prisma.calendarSync.delete({
|
|
where: { id: calToDelete.syncConfig.id },
|
|
});
|
|
console.log(` ✅ Deleted sync config ${calToDelete.syncConfig.id}`);
|
|
}
|
|
|
|
// Delete calendar (events are already migrated or deleted)
|
|
await prisma.calendar.delete({
|
|
where: { id: calToDelete.id },
|
|
});
|
|
console.log(` ✅ Deleted calendar ${calToDelete.id}`);
|
|
}
|
|
|
|
console.log(`\n✅ Consolidation completed!`);
|
|
console.log(` Kept: 1 calendar (${calendarToKeep.id})`);
|
|
console.log(` Deleted: ${calendarsToDelete.length} duplicate calendars`);
|
|
console.log(` Migrated: ${totalMigrated} events\n`);
|
|
|
|
} catch (error) {
|
|
console.error('❌ Error consolidating calendars:', error);
|
|
throw error;
|
|
} finally {
|
|
await prisma.$disconnect();
|
|
}
|
|
}
|
|
|
|
// Run the script
|
|
consolidatePrivateCalendars()
|
|
.then(() => {
|
|
console.log('✅ Script completed successfully');
|
|
process.exit(0);
|
|
})
|
|
.catch((error) => {
|
|
console.error('❌ Script failed:', error);
|
|
process.exit(1);
|
|
});
|