Agenda refactor

This commit is contained in:
alma 2026-01-15 12:38:39 +01:00
parent 1d2a72ce8c
commit fe6655f913
4 changed files with 180 additions and 42 deletions

View File

@ -595,6 +595,7 @@ export default async function CalendarPage() {
}
// Auto-sync Infomaniak calendars if needed (background, don't block page load)
// Reload sync configs after URL corrections to get updated URLs
const infomaniakSyncConfigs = await prisma.calendarSync.findMany({
where: {
provider: 'infomaniak',
@ -609,6 +610,12 @@ export default async function CalendarPage() {
// Trigger sync for Infomaniak calendars that need it (async, don't wait)
for (const syncConfig of infomaniakSyncConfigs) {
// Skip sync if URL is still invalid (should have been fixed above, but double-check)
if (syncConfig.externalCalendarUrl === '/principals' || !syncConfig.externalCalendarUrl || syncConfig.externalCalendarUrl === '/') {
console.log(`[AGENDA] Skipping Infomaniak sync ${syncConfig.id} - invalid calendar URL: ${syncConfig.externalCalendarUrl}`);
continue;
}
const minutesSinceLastSync = syncConfig.lastSyncAt
? (Date.now() - syncConfig.lastSyncAt.getTime()) / (1000 * 60)
: Infinity;
@ -617,7 +624,7 @@ export default async function CalendarPage() {
const needsSync = !syncConfig.lastSyncAt ||
minutesSinceLastSync >= syncConfig.syncFrequency;
console.log(`[AGENDA] Infomaniak sync config ${syncConfig.id}: lastSyncAt=${syncConfig.lastSyncAt}, minutesSinceLastSync=${minutesSinceLastSync.toFixed(1)}, needsSync=${needsSync}, syncFrequency=${syncConfig.syncFrequency}`);
console.log(`[AGENDA] Infomaniak sync config ${syncConfig.id}: lastSyncAt=${syncConfig.lastSyncAt}, minutesSinceLastSync=${minutesSinceLastSync.toFixed(1)}, needsSync=${needsSync}, syncFrequency=${syncConfig.syncFrequency}, calendarUrl=${syncConfig.externalCalendarUrl}`);
if (needsSync) {
console.log(`[AGENDA] Triggering background sync for Infomaniak calendar ${syncConfig.id}`);

View File

@ -453,25 +453,49 @@ export async function syncInfomaniakCalendar(
// For updates, we cannot modify calendarId and userId (they are relations)
// For creates, we need them
const baseEventData = {
// Build event data dynamically to handle case where externalEventId field doesn't exist yet
const baseEventData: any = {
title: caldavEvent.summary,
description: caldavEvent.description || null,
start: caldavEvent.start,
end: caldavEvent.end,
location: caldavEvent.location || null,
isAllDay: caldavEvent.allDay,
externalEventId: caldavEvent.uid, // Store UID for reliable matching
};
// Only add externalEventId if migration has been applied
// We'll try to add it, and if it fails, we'll retry without it
if (caldavEvent.uid) {
baseEventData.externalEventId = caldavEvent.uid;
}
if (existingEvent) {
// Update existing event (without calendarId and userId - they are relations)
try {
await prisma.event.update({
where: { id: existingEvent.id },
data: baseEventData,
});
updated++;
} catch (updateError: any) {
// If externalEventId field doesn't exist, retry without it
if (updateError?.message?.includes('externalEventId') || updateError?.code === 'P2009') {
logger.warn('externalEventId field not available, updating without it', {
eventId: existingEvent.id,
});
const { externalEventId, ...dataWithoutExternalId } = baseEventData;
await prisma.event.update({
where: { id: existingEvent.id },
data: dataWithoutExternalId,
});
updated++;
} else {
throw updateError;
}
}
} else {
// Create new event (with calendarId and userId)
try {
await prisma.event.create({
data: {
...baseEventData,
@ -480,6 +504,23 @@ export async function syncInfomaniakCalendar(
},
});
created++;
} catch (createError: any) {
// If externalEventId field doesn't exist, retry without it
if (createError?.message?.includes('externalEventId') || createError?.code === 'P2009') {
logger.warn('externalEventId field not available, creating without it');
const { externalEventId, ...dataWithoutExternalId } = baseEventData;
await prisma.event.create({
data: {
...dataWithoutExternalId,
calendarId: syncConfig.calendarId,
userId: syncConfig.calendar.userId,
},
});
created++;
} else {
throw createError;
}
}
}
}

View File

@ -499,18 +499,25 @@ export async function syncMicrosoftCalendar(
// For updates, we cannot modify calendarId and userId (they are relations)
// For creates, we need them
const baseEventData = {
// Build event data dynamically to handle case where externalEventId field doesn't exist yet
const baseEventData: any = {
title: caldavEvent.summary,
description: cleanedDescription, // Clean description without [MS_ID:xxx] prefix
start: caldavEvent.start,
end: caldavEvent.end,
location: caldavEvent.location || null,
isAllDay: caldavEvent.allDay,
externalEventId: microsoftId, // Store Microsoft ID for reliable matching
};
// Only add externalEventId if migration has been applied
// We'll try to add it, and if it fails, we'll retry without it
if (microsoftId) {
baseEventData.externalEventId = microsoftId;
}
if (existingEvent) {
// Update existing event (without calendarId and userId - they are relations)
try {
await prisma.event.update({
where: { id: existingEvent.id },
data: baseEventData,
@ -521,8 +528,25 @@ export async function syncMicrosoftCalendar(
title: caldavEvent.summary,
microsoftId,
});
} catch (updateError: any) {
// If externalEventId field doesn't exist, retry without it
if (updateError?.message?.includes('externalEventId') || updateError?.code === 'P2009') {
logger.warn('externalEventId field not available, updating without it', {
eventId: existingEvent.id,
});
const { externalEventId, ...dataWithoutExternalId } = baseEventData;
await prisma.event.update({
where: { id: existingEvent.id },
data: dataWithoutExternalId,
});
updated++;
} else {
throw updateError;
}
}
} else {
// Create new event (with calendarId and userId)
try {
const newEvent = await prisma.event.create({
data: {
...baseEventData,
@ -537,6 +561,23 @@ export async function syncMicrosoftCalendar(
microsoftId,
start: caldavEvent.start.toISOString(),
});
} catch (createError: any) {
// If externalEventId field doesn't exist, retry without it
if (createError?.message?.includes('externalEventId') || createError?.code === 'P2009') {
logger.warn('externalEventId field not available, creating without it');
const { externalEventId, ...dataWithoutExternalId } = baseEventData;
const newEvent = await prisma.event.create({
data: {
...dataWithoutExternalId,
calendarId: syncConfig.calendarId,
userId: syncConfig.calendar.userId,
},
});
created++;
} else {
throw createError;
}
}
}
}

View File

@ -0,0 +1,49 @@
-- Script to manually apply externalEventId migration
-- Run this directly on your PostgreSQL database
-- Check if columns exist
SELECT
column_name,
data_type
FROM information_schema.columns
WHERE table_name = 'Event'
AND column_name IN ('externalEventId', 'externalEventUrl');
-- Add externalEventId column if it doesn't exist
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'Event' AND column_name = 'externalEventId'
) THEN
ALTER TABLE "Event" ADD COLUMN "externalEventId" TEXT;
RAISE NOTICE 'Added externalEventId column';
ELSE
RAISE NOTICE 'externalEventId column already exists';
END IF;
END $$;
-- Add externalEventUrl column if it doesn't exist
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'Event' AND column_name = 'externalEventUrl'
) THEN
ALTER TABLE "Event" ADD COLUMN "externalEventUrl" TEXT;
RAISE NOTICE 'Added externalEventUrl column';
ELSE
RAISE NOTICE 'externalEventUrl column already exists';
END IF;
END $$;
-- Create index on externalEventId if it doesn't exist
CREATE INDEX IF NOT EXISTS "Event_externalEventId_idx" ON "Event"("externalEventId");
-- Verify columns were added
SELECT
column_name,
data_type
FROM information_schema.columns
WHERE table_name = 'Event'
AND column_name IN ('externalEventId', 'externalEventUrl');