Agenda refactor
This commit is contained in:
parent
631524b8a8
commit
2ef0717e9c
@ -507,7 +507,8 @@ export async function syncMicrosoftCalendar(
|
||||
|
||||
// Priority 3: Fallback to title + date matching for events without externalEventId
|
||||
// IMPORTANT: Only match if the event doesn't have an externalEventId (to avoid false matches)
|
||||
if (!existingEvent) {
|
||||
// This helps migrate old events that were created before externalEventId was added
|
||||
if (!existingEvent && microsoftId) {
|
||||
existingEvent = existingEvents.find(
|
||||
(e) => {
|
||||
// Access externalEventId safely (may not be in Prisma type if client not regenerated)
|
||||
@ -521,10 +522,11 @@ export async function syncMicrosoftCalendar(
|
||||
if (e.title === caldavEvent.summary) {
|
||||
const timeDiff = Math.abs(new Date(e.start).getTime() - caldavEvent.start.getTime());
|
||||
if (timeDiff < 60000) { // Within 1 minute
|
||||
logger.debug('Matched event by title + date (no externalEventId)', {
|
||||
logger.debug('Matched event by title + date (no externalEventId) - will update with externalEventId', {
|
||||
eventId: e.id,
|
||||
title: caldavEvent.summary,
|
||||
timeDiff,
|
||||
microsoftId,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
145
scripts/cleanup-duplicate-events.ts
Normal file
145
scripts/cleanup-duplicate-events.ts
Normal file
@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env ts-node
|
||||
|
||||
/**
|
||||
* Script to clean up duplicate events in the database
|
||||
* Keeps events with externalEventId, removes duplicates without externalEventId
|
||||
*/
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
interface DuplicateGroup {
|
||||
title: string;
|
||||
start: Date;
|
||||
end: Date;
|
||||
calendarId: string;
|
||||
events: Array<{
|
||||
id: string;
|
||||
externalEventId: string | null;
|
||||
}>;
|
||||
}
|
||||
|
||||
async function cleanupDuplicateEvents() {
|
||||
try {
|
||||
console.log('🔍 Searching for duplicate events...\n');
|
||||
|
||||
// Find all events grouped by title, start, end, and calendarId
|
||||
const allEvents = await prisma.event.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
start: true,
|
||||
end: true,
|
||||
calendarId: true,
|
||||
externalEventId: true,
|
||||
},
|
||||
orderBy: [
|
||||
{ calendarId: 'asc' },
|
||||
{ title: 'asc' },
|
||||
{ start: 'asc' },
|
||||
],
|
||||
});
|
||||
|
||||
// Group events by title + start + end + calendarId
|
||||
const eventGroups = new Map<string, DuplicateGroup>();
|
||||
|
||||
for (const event of allEvents) {
|
||||
const key = `${event.calendarId}|${event.title}|${event.start.toISOString()}|${event.end.toISOString()}`;
|
||||
|
||||
if (!eventGroups.has(key)) {
|
||||
eventGroups.set(key, {
|
||||
title: event.title,
|
||||
start: event.start,
|
||||
end: event.end,
|
||||
calendarId: event.calendarId,
|
||||
events: [],
|
||||
});
|
||||
}
|
||||
|
||||
eventGroups.get(key)!.events.push({
|
||||
id: event.id,
|
||||
externalEventId: event.externalEventId,
|
||||
});
|
||||
}
|
||||
|
||||
// Find duplicates (groups with more than 1 event)
|
||||
const duplicates: DuplicateGroup[] = [];
|
||||
for (const group of eventGroups.values()) {
|
||||
if (group.events.length > 1) {
|
||||
duplicates.push(group);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Found ${duplicates.length} groups of duplicate events\n`);
|
||||
|
||||
if (duplicates.length === 0) {
|
||||
console.log('✅ No duplicates found!');
|
||||
return;
|
||||
}
|
||||
|
||||
let totalDeleted = 0;
|
||||
let totalKept = 0;
|
||||
|
||||
for (const group of duplicates) {
|
||||
console.log(`\n📋 Processing: "${group.title}" (${group.events.length} duplicates)`);
|
||||
console.log(` Calendar: ${group.calendarId}`);
|
||||
console.log(` Date: ${group.start.toISOString()}`);
|
||||
|
||||
// Separate events with and without externalEventId
|
||||
const withExternalId = group.events.filter(e => e.externalEventId);
|
||||
const withoutExternalId = group.events.filter(e => !e.externalEventId);
|
||||
|
||||
if (withExternalId.length > 1) {
|
||||
// Multiple events with externalEventId - keep the first one, delete others
|
||||
console.log(` ⚠️ Warning: Multiple events with externalEventId found!`);
|
||||
const toKeep = withExternalId[0];
|
||||
const toDelete = [...withExternalId.slice(1), ...withoutExternalId];
|
||||
|
||||
console.log(` ✅ Keeping: ${toKeep.id} (has externalEventId)`);
|
||||
for (const event of toDelete) {
|
||||
console.log(` 🗑️ Deleting: ${event.id}`);
|
||||
await prisma.event.delete({ where: { id: event.id } });
|
||||
totalDeleted++;
|
||||
}
|
||||
totalKept++;
|
||||
} else if (withExternalId.length === 1) {
|
||||
// One event with externalEventId - keep it, delete others
|
||||
const toKeep = withExternalId[0];
|
||||
const toDelete = withoutExternalId;
|
||||
|
||||
console.log(` ✅ Keeping: ${toKeep.id} (has externalEventId)`);
|
||||
for (const event of toDelete) {
|
||||
console.log(` 🗑️ Deleting: ${event.id}`);
|
||||
await prisma.event.delete({ where: { id: event.id } });
|
||||
totalDeleted++;
|
||||
}
|
||||
totalKept++;
|
||||
} else {
|
||||
// No events with externalEventId - keep the first one, delete others
|
||||
const toKeep = group.events[0];
|
||||
const toDelete = group.events.slice(1);
|
||||
|
||||
console.log(` ✅ Keeping: ${toKeep.id} (no externalEventId, keeping first)`);
|
||||
for (const event of toDelete) {
|
||||
console.log(` 🗑️ Deleting: ${event.id}`);
|
||||
await prisma.event.delete({ where: { id: event.id } });
|
||||
totalDeleted++;
|
||||
}
|
||||
totalKept++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n✅ Cleanup completed!`);
|
||||
console.log(` Kept: ${totalKept} events`);
|
||||
console.log(` Deleted: ${totalDeleted} duplicate events`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error cleaning up duplicates:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
cleanupDuplicateEvents();
|
||||
Loading…
Reference in New Issue
Block a user