Agenda refactor
This commit is contained in:
parent
27b9d5df7d
commit
23e2e6c058
@ -46,12 +46,26 @@ export function Calendar() {
|
||||
end: event.end,
|
||||
allDay: event.isAllDay,
|
||||
calendar: calendar.name,
|
||||
calendarColor: calendar.color
|
||||
calendarColor: calendar.color,
|
||||
externalEventId: event.externalEventId || null, // Add externalEventId for deduplication
|
||||
}))
|
||||
);
|
||||
|
||||
// Deduplicate events by externalEventId (if same event appears in multiple calendars)
|
||||
// Keep the first occurrence of each unique externalEventId
|
||||
const seenExternalIds = new Set<string>();
|
||||
const deduplicatedEvents = allEvents.filter((event: any) => {
|
||||
if (event.externalEventId) {
|
||||
if (seenExternalIds.has(event.externalEventId)) {
|
||||
return false; // Skip duplicate
|
||||
}
|
||||
seenExternalIds.add(event.externalEventId);
|
||||
}
|
||||
return true; // Keep event (either has no externalEventId or is first occurrence)
|
||||
});
|
||||
|
||||
// Filter for upcoming events
|
||||
const upcomingEvents = allEvents
|
||||
const upcomingEvents = deduplicatedEvents
|
||||
.filter((event: any) => new Date(event.start) >= now)
|
||||
.sort((a: any, b: any) => new Date(a.start).getTime() - new Date(b.start).getTime())
|
||||
.slice(0, 7);
|
||||
|
||||
@ -18,12 +18,14 @@ export interface MicrosoftEvent {
|
||||
contentType?: string;
|
||||
};
|
||||
start: {
|
||||
dateTime: string;
|
||||
timeZone: string;
|
||||
dateTime?: string; // For timed events
|
||||
date?: string; // For all-day events (YYYY-MM-DD format)
|
||||
timeZone?: string;
|
||||
};
|
||||
end: {
|
||||
dateTime: string;
|
||||
timeZone: string;
|
||||
dateTime?: string; // For timed events
|
||||
date?: string; // For all-day events (YYYY-MM-DD format)
|
||||
timeZone?: string;
|
||||
};
|
||||
location?: {
|
||||
displayName?: string;
|
||||
@ -138,24 +140,30 @@ export async function fetchMicrosoftEvents(
|
||||
};
|
||||
|
||||
// Add date filter if provided
|
||||
// Note: Microsoft Graph API filter syntax is limited
|
||||
// We can't easily filter both dateTime and date in one query
|
||||
// So we'll filter by dateTime and handle all-day events separately if needed
|
||||
// Microsoft Graph API supports filtering by start/dateTime for timed events
|
||||
// For all-day events, we need to filter by start/date (date only, no time)
|
||||
// We'll use a combined filter that handles both cases
|
||||
if (startDate && endDate) {
|
||||
// Format dates for Microsoft Graph API
|
||||
// Use ISO 8601 format for dateTime filter
|
||||
// For dateTime filter: use ISO 8601 format
|
||||
const startDateTimeStr = startDate.toISOString();
|
||||
const endDateTimeStr = endDate.toISOString();
|
||||
|
||||
// Microsoft Graph API filter: filter by start/dateTime
|
||||
// This will match timed events. All-day events might need separate handling
|
||||
// but Microsoft Graph usually returns all-day events with dateTime set to start of day
|
||||
params.$filter = `start/dateTime ge '${startDateTimeStr}' and start/dateTime le '${endDateTimeStr}'`;
|
||||
// For date filter (all-day events): use YYYY-MM-DD format
|
||||
const startDateStr = startDate.toISOString().split('T')[0];
|
||||
const endDateStr = endDate.toISOString().split('T')[0];
|
||||
|
||||
// Combined filter: (start/dateTime >= startDate OR start/date >= startDate)
|
||||
// AND (start/dateTime <= endDate OR start/date <= endDate)
|
||||
// This handles both timed and all-day events
|
||||
params.$filter = `(start/dateTime ge '${startDateTimeStr}' or start/date ge '${startDateStr}') and (start/dateTime le '${endDateTimeStr}' or start/date le '${endDateStr}')`;
|
||||
|
||||
logger.debug('Microsoft Graph API filter', {
|
||||
filter: params.$filter,
|
||||
startDateTime: startDateTimeStr,
|
||||
endDateTime: endDateTimeStr,
|
||||
startDate: startDateStr,
|
||||
endDate: endDateStr,
|
||||
});
|
||||
} else {
|
||||
// If no date filter, get all events (might be too many, but useful for debugging)
|
||||
@ -192,7 +200,7 @@ export async function fetchMicrosoftEvents(
|
||||
hasValue: !!response.data.value,
|
||||
status: response.status,
|
||||
// Log first few event IDs to verify they're being returned
|
||||
eventIds: events.slice(0, 5).map(e => e.id),
|
||||
eventIds: events.slice(0, 5).map((e: MicrosoftEvent) => e.id),
|
||||
});
|
||||
|
||||
// Log if we got fewer events than expected
|
||||
@ -243,23 +251,30 @@ export function convertMicrosoftEventToCalDAV(microsoftEvent: MicrosoftEvent): {
|
||||
allDay: boolean;
|
||||
} {
|
||||
// Microsoft Graph API uses different formats for all-day vs timed events
|
||||
// All-day events have dateTime in format "YYYY-MM-DD" without time
|
||||
// Timed events have dateTime in ISO format with time
|
||||
const isAllDay = microsoftEvent.isAllDay ||
|
||||
(microsoftEvent.start.dateTime && !microsoftEvent.start.dateTime.includes('T'));
|
||||
// All-day events have 'date' field (YYYY-MM-DD format) or isAllDay=true
|
||||
// Timed events have 'dateTime' field (ISO format with time)
|
||||
const isAllDay = microsoftEvent.isAllDay || !!microsoftEvent.start.date;
|
||||
|
||||
let startDate: Date;
|
||||
let endDate: Date;
|
||||
|
||||
if (isAllDay) {
|
||||
// For all-day events, parse date only (YYYY-MM-DD)
|
||||
startDate = new Date(microsoftEvent.start.dateTime.split('T')[0]);
|
||||
endDate = new Date(microsoftEvent.end.dateTime.split('T')[0]);
|
||||
// For all-day events, use 'date' field or parse dateTime if date not available
|
||||
const startDateStr = microsoftEvent.start.date || microsoftEvent.start.dateTime?.split('T')[0] || '';
|
||||
const endDateStr = microsoftEvent.end.date || microsoftEvent.end.dateTime?.split('T')[0] || '';
|
||||
startDate = new Date(startDateStr);
|
||||
endDate = new Date(endDateStr);
|
||||
// Set to start of day
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
endDate.setHours(0, 0, 0, 0);
|
||||
} else {
|
||||
// For timed events, parse full ISO datetime
|
||||
if (!microsoftEvent.start.dateTime) {
|
||||
throw new Error(`Timed event missing dateTime: ${JSON.stringify(microsoftEvent.start)}`);
|
||||
}
|
||||
if (!microsoftEvent.end.dateTime) {
|
||||
throw new Error(`Timed event missing end dateTime: ${JSON.stringify(microsoftEvent.end)}`);
|
||||
}
|
||||
startDate = new Date(microsoftEvent.start.dateTime);
|
||||
endDate = new Date(microsoftEvent.end.dateTime);
|
||||
}
|
||||
@ -351,7 +366,7 @@ export async function syncMicrosoftCalendar(
|
||||
start: startDate.toISOString(),
|
||||
end: endDate.toISOString()
|
||||
},
|
||||
allEvents: microsoftEvents.map(e => ({
|
||||
allEvents: microsoftEvents.map((e: MicrosoftEvent) => ({
|
||||
id: e.id,
|
||||
subject: e.subject || '(sans titre)',
|
||||
start: e.start.dateTime || e.start.date,
|
||||
@ -363,25 +378,27 @@ export async function syncMicrosoftCalendar(
|
||||
})),
|
||||
});
|
||||
|
||||
// Check if "Test" event is in the list
|
||||
const testEvent = microsoftEvents.find(e =>
|
||||
e.subject && e.subject.toLowerCase().includes('test')
|
||||
// Check if "test" or "retest" events are in the list
|
||||
const testEvents = microsoftEvents.filter((e: MicrosoftEvent) =>
|
||||
e.subject && (e.subject.toLowerCase().includes('test') || e.subject.toLowerCase().includes('retest'))
|
||||
);
|
||||
if (testEvent) {
|
||||
logger.info('Found "Test" event in Microsoft response', {
|
||||
if (testEvents.length > 0) {
|
||||
logger.info('Found "test"/"retest" events in Microsoft response', {
|
||||
calendarSyncId,
|
||||
testEvent: {
|
||||
id: testEvent.id,
|
||||
subject: testEvent.subject,
|
||||
start: testEvent.start.dateTime || testEvent.start.date,
|
||||
isAllDay: testEvent.isAllDay,
|
||||
}
|
||||
count: testEvents.length,
|
||||
events: testEvents.map((e: MicrosoftEvent) => ({
|
||||
id: e.id,
|
||||
subject: e.subject,
|
||||
start: e.start.dateTime || e.start.date,
|
||||
isAllDay: e.isAllDay,
|
||||
}))
|
||||
});
|
||||
} else {
|
||||
logger.warn('"Test" event NOT found in Microsoft response', {
|
||||
logger.warn('"test"/"retest" events NOT found in Microsoft response', {
|
||||
calendarSyncId,
|
||||
totalEvents: microsoftEvents.length,
|
||||
eventSubjects: microsoftEvents.map(e => e.subject || '(sans titre)'),
|
||||
dateRange: { start: startDate.toISOString(), end: endDate.toISOString() },
|
||||
eventSubjects: microsoftEvents.map((e: MicrosoftEvent) => e.subject || '(sans titre)').slice(0, 10), // First 10 for debugging
|
||||
});
|
||||
}
|
||||
|
||||
@ -395,15 +412,16 @@ export async function syncMicrosoftCalendar(
|
||||
} else {
|
||||
// Log events in the future to help debug
|
||||
const now = new Date();
|
||||
const futureEvents = microsoftEvents.filter(e => {
|
||||
const futureEvents = microsoftEvents.filter((e: MicrosoftEvent) => {
|
||||
const eventStart = e.start.dateTime || e.start.date;
|
||||
if (!eventStart) return false;
|
||||
return new Date(eventStart) > now;
|
||||
});
|
||||
logger.info('Microsoft events in the future', {
|
||||
calendarSyncId,
|
||||
futureEventCount: futureEvents.length,
|
||||
totalEvents: microsoftEvents.length,
|
||||
futureEvents: futureEvents.map(e => ({
|
||||
futureEvents: futureEvents.map((e: MicrosoftEvent) => ({
|
||||
id: e.id,
|
||||
subject: e.subject || '(sans titre)',
|
||||
start: e.start.dateTime || e.start.date,
|
||||
@ -412,15 +430,16 @@ export async function syncMicrosoftCalendar(
|
||||
});
|
||||
|
||||
// Also log events in the past to see all events
|
||||
const pastEvents = microsoftEvents.filter(e => {
|
||||
const pastEvents = microsoftEvents.filter((e: MicrosoftEvent) => {
|
||||
const eventStart = e.start.dateTime || e.start.date;
|
||||
if (!eventStart) return false;
|
||||
return new Date(eventStart) <= now;
|
||||
});
|
||||
if (pastEvents.length > 0) {
|
||||
logger.info('Microsoft events in the past', {
|
||||
calendarSyncId,
|
||||
pastEventCount: pastEvents.length,
|
||||
pastEvents: pastEvents.map(e => ({
|
||||
pastEvents: pastEvents.map((e: MicrosoftEvent) => ({
|
||||
id: e.id,
|
||||
subject: e.subject || '(sans titre)',
|
||||
start: e.start.dateTime || e.start.date,
|
||||
@ -487,7 +506,7 @@ export async function syncMicrosoftCalendar(
|
||||
|
||||
// Priority 2: Fallback to checking description for [MS_ID:xxx] (backward compatibility)
|
||||
if (!existingEvent && microsoftId) {
|
||||
existingEvent = existingEvents.find((e) => {
|
||||
existingEvent = existingEvents.find((e: typeof existingEvents[0]) => {
|
||||
// Access externalEventId safely (may not be in Prisma type if client not regenerated)
|
||||
const hasExternalId = !!(e as any).externalEventId;
|
||||
if (!hasExternalId && e.description && e.description.includes(`[MS_ID:${microsoftId}]`)) {
|
||||
@ -510,7 +529,7 @@ export async function syncMicrosoftCalendar(
|
||||
// This helps migrate old events that were created before externalEventId was added
|
||||
if (!existingEvent && microsoftId) {
|
||||
existingEvent = existingEvents.find(
|
||||
(e) => {
|
||||
(e: typeof existingEvents[0]) => {
|
||||
// Access externalEventId safely (may not be in Prisma type if client not regenerated)
|
||||
const hasExternalId = !!(e as any).externalEventId;
|
||||
// Only match events that don't have externalEventId yet (to avoid false matches)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user