Agenda refactor
This commit is contained in:
parent
2ef0717e9c
commit
27b9d5df7d
57
scripts/cleanup-duplicate-events.sql
Normal file
57
scripts/cleanup-duplicate-events.sql
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
-- Script SQL to clean up duplicate events
|
||||||
|
-- Keeps events with externalEventId, removes duplicates without externalEventId
|
||||||
|
-- For events with same title, calendarId, and date (for all-day events) or same start/end (for timed events)
|
||||||
|
|
||||||
|
-- First, let's see the duplicates
|
||||||
|
SELECT
|
||||||
|
"calendarId",
|
||||||
|
title,
|
||||||
|
DATE("start") as start_date,
|
||||||
|
DATE("end") as end_date,
|
||||||
|
COUNT(*) as duplicate_count,
|
||||||
|
COUNT(CASE WHEN "externalEventId" IS NOT NULL THEN 1 END) as with_external_id,
|
||||||
|
COUNT(CASE WHEN "externalEventId" IS NULL THEN 1 END) as without_external_id
|
||||||
|
FROM "Event"
|
||||||
|
WHERE title IN ('Vendredi saint', 'Dimanche de Pâques')
|
||||||
|
GROUP BY "calendarId", title, DATE("start"), DATE("end")
|
||||||
|
HAVING COUNT(*) > 1
|
||||||
|
ORDER BY title, start_date;
|
||||||
|
|
||||||
|
-- Delete duplicates: keep the one with externalEventId, or the oldest one if none have externalEventId
|
||||||
|
WITH duplicates AS (
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
"calendarId",
|
||||||
|
title,
|
||||||
|
"start",
|
||||||
|
"end",
|
||||||
|
"externalEventId",
|
||||||
|
ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY
|
||||||
|
"calendarId",
|
||||||
|
title,
|
||||||
|
DATE("start"),
|
||||||
|
DATE("end")
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN "externalEventId" IS NOT NULL THEN 0 ELSE 1 END, -- Prefer events with externalEventId
|
||||||
|
"createdAt" ASC -- If no externalEventId, keep the oldest
|
||||||
|
) as rn
|
||||||
|
FROM "Event"
|
||||||
|
WHERE title IN ('Vendredi saint', 'Dimanche de Pâques')
|
||||||
|
)
|
||||||
|
DELETE FROM "Event"
|
||||||
|
WHERE id IN (
|
||||||
|
SELECT id FROM duplicates WHERE rn > 1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Verify cleanup
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
"calendarId",
|
||||||
|
"externalEventId",
|
||||||
|
"start",
|
||||||
|
"end"
|
||||||
|
FROM "Event"
|
||||||
|
WHERE title IN ('Vendredi saint', 'Dimanche de Pâques')
|
||||||
|
ORDER BY title, "start";
|
||||||
@ -33,6 +33,7 @@ async function cleanupDuplicateEvents() {
|
|||||||
end: true,
|
end: true,
|
||||||
calendarId: true,
|
calendarId: true,
|
||||||
externalEventId: true,
|
externalEventId: true,
|
||||||
|
isAllDay: true,
|
||||||
},
|
},
|
||||||
orderBy: [
|
orderBy: [
|
||||||
{ calendarId: 'asc' },
|
{ calendarId: 'asc' },
|
||||||
@ -42,10 +43,49 @@ async function cleanupDuplicateEvents() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Group events by title + start + end + calendarId
|
// Group events by title + start + end + calendarId
|
||||||
|
// For all-day events, compare by date only (YYYY-MM-DD), not exact time
|
||||||
const eventGroups = new Map<string, DuplicateGroup>();
|
const eventGroups = new Map<string, DuplicateGroup>();
|
||||||
|
|
||||||
for (const event of allEvents) {
|
for (const event of allEvents) {
|
||||||
const key = `${event.calendarId}|${event.title}|${event.start.toISOString()}|${event.end.toISOString()}`;
|
// Normalize dates to compare: for all-day events, use date only
|
||||||
|
// For timed events, round to nearest minute to handle small time differences
|
||||||
|
const startDate = new Date(event.start);
|
||||||
|
const endDate = new Date(event.end);
|
||||||
|
|
||||||
|
// Check if it's an all-day event (use isAllDay field or detect by duration)
|
||||||
|
const duration = endDate.getTime() - startDate.getTime();
|
||||||
|
const isAllDay = event.isAllDay ||
|
||||||
|
(duration >= 23 * 60 * 60 * 1000 && duration <= 25 * 60 * 60 * 1000); // 23-25 hours
|
||||||
|
|
||||||
|
let startKey: string;
|
||||||
|
let endKey: string;
|
||||||
|
|
||||||
|
if (isAllDay) {
|
||||||
|
// For all-day events, compare by date only (YYYY-MM-DD)
|
||||||
|
// Use UTC date to avoid timezone issues
|
||||||
|
const startUTC = new Date(Date.UTC(
|
||||||
|
startDate.getUTCFullYear(),
|
||||||
|
startDate.getUTCMonth(),
|
||||||
|
startDate.getUTCDate()
|
||||||
|
));
|
||||||
|
const endUTC = new Date(Date.UTC(
|
||||||
|
endDate.getUTCFullYear(),
|
||||||
|
endDate.getUTCMonth(),
|
||||||
|
endDate.getUTCDate()
|
||||||
|
));
|
||||||
|
startKey = `${startUTC.getUTCFullYear()}-${String(startUTC.getUTCMonth() + 1).padStart(2, '0')}-${String(startUTC.getUTCDate()).padStart(2, '0')}`;
|
||||||
|
endKey = `${endUTC.getUTCFullYear()}-${String(endUTC.getUTCMonth() + 1).padStart(2, '0')}-${String(endUTC.getUTCDate()).padStart(2, '0')}`;
|
||||||
|
} else {
|
||||||
|
// For timed events, round to nearest minute to handle small differences
|
||||||
|
const startRounded = new Date(startDate);
|
||||||
|
startRounded.setSeconds(0, 0);
|
||||||
|
const endRounded = new Date(endDate);
|
||||||
|
endRounded.setSeconds(0, 0);
|
||||||
|
startKey = startRounded.toISOString();
|
||||||
|
endKey = endRounded.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = `${event.calendarId}|${event.title}|${startKey}|${endKey}`;
|
||||||
|
|
||||||
if (!eventGroups.has(key)) {
|
if (!eventGroups.has(key)) {
|
||||||
eventGroups.set(key, {
|
eventGroups.set(key, {
|
||||||
@ -73,8 +113,58 @@ async function cleanupDuplicateEvents() {
|
|||||||
|
|
||||||
console.log(`Found ${duplicates.length} groups of duplicate events\n`);
|
console.log(`Found ${duplicates.length} groups of duplicate events\n`);
|
||||||
|
|
||||||
|
// Debug: Show all events for specific titles to understand grouping
|
||||||
|
const debugTitles = ['Vendredi saint', 'Dimanche de Pâques'];
|
||||||
|
for (const title of debugTitles) {
|
||||||
|
const titleEvents = allEvents.filter(e => e.title === title);
|
||||||
|
if (titleEvents.length > 0) {
|
||||||
|
console.log(`\n🔍 Debug: Events for "${title}":`);
|
||||||
|
for (const evt of titleEvents) {
|
||||||
|
const startDate = new Date(evt.start);
|
||||||
|
const endDate = new Date(evt.end);
|
||||||
|
const duration = endDate.getTime() - startDate.getTime();
|
||||||
|
const isAllDay = evt.isAllDay || (duration >= 23 * 60 * 60 * 1000 && duration <= 25 * 60 * 60 * 1000);
|
||||||
|
console.log(` - ID: ${evt.id}`);
|
||||||
|
console.log(` Calendar: ${evt.calendarId}`);
|
||||||
|
console.log(` Start: ${evt.start.toISOString()} (${startDate.toLocaleDateString()})`);
|
||||||
|
console.log(` End: ${evt.end.toISOString()} (${endDate.toLocaleDateString()})`);
|
||||||
|
console.log(` Duration: ${duration / (60 * 60 * 1000)} hours`);
|
||||||
|
console.log(` isAllDay: ${evt.isAllDay}, detected: ${isAllDay}`);
|
||||||
|
console.log(` externalEventId: ${evt.externalEventId ? 'YES' : 'NO'}`);
|
||||||
|
|
||||||
|
// Show what key would be generated
|
||||||
|
let startKey: string;
|
||||||
|
let endKey: string;
|
||||||
|
if (isAllDay) {
|
||||||
|
const startUTC = new Date(Date.UTC(
|
||||||
|
startDate.getUTCFullYear(),
|
||||||
|
startDate.getUTCMonth(),
|
||||||
|
startDate.getUTCDate()
|
||||||
|
));
|
||||||
|
const endUTC = new Date(Date.UTC(
|
||||||
|
endDate.getUTCFullYear(),
|
||||||
|
endDate.getUTCMonth(),
|
||||||
|
endDate.getUTCDate()
|
||||||
|
));
|
||||||
|
startKey = `${startUTC.getUTCFullYear()}-${String(startUTC.getUTCMonth() + 1).padStart(2, '0')}-${String(startUTC.getUTCDate()).padStart(2, '0')}`;
|
||||||
|
endKey = `${endUTC.getUTCFullYear()}-${String(endUTC.getUTCMonth() + 1).padStart(2, '0')}-${String(endUTC.getUTCDate()).padStart(2, '0')}`;
|
||||||
|
} else {
|
||||||
|
const startRounded = new Date(startDate);
|
||||||
|
startRounded.setSeconds(0, 0);
|
||||||
|
const endRounded = new Date(endDate);
|
||||||
|
endRounded.setSeconds(0, 0);
|
||||||
|
startKey = startRounded.toISOString();
|
||||||
|
endKey = endRounded.toISOString();
|
||||||
|
}
|
||||||
|
const key = `${evt.calendarId}|${evt.title}|${startKey}|${endKey}`;
|
||||||
|
console.log(` Group key: ${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (duplicates.length === 0) {
|
if (duplicates.length === 0) {
|
||||||
console.log('✅ No duplicates found!');
|
console.log('\n✅ No duplicates found!');
|
||||||
|
console.log(' (Check debug output above to see why events are not grouped as duplicates)');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user