diff --git a/app/agenda/page.tsx b/app/agenda/page.tsx index 6642c10..f48885c 100644 --- a/app/agenda/page.tsx +++ b/app/agenda/page.tsx @@ -63,7 +63,8 @@ export default async function CalendarPage() { { name: { in: ["Privée", "Default"] } }, { syncConfig: { - isNot: null + isNot: null, + syncEnabled: true // Also check that sync is enabled } } ] @@ -140,6 +141,19 @@ export default async function CalendarPage() { // Extract mission calendars const missionCalendars = missionUserRelations.flatMap(mu => mu.mission.calendars); + // Debug: Log calendar filtering + console.log('[AGENDA] Calendar filtering:', { + personalCalendarsCount: personalCalendars.length, + personalCalendars: personalCalendars.map(cal => ({ + id: cal.id, + name: cal.name, + hasSyncConfig: !!cal.syncConfig, + syncEnabled: cal.syncConfig?.syncEnabled, + provider: cal.syncConfig?.provider, + })), + missionCalendarsCount: missionCalendars.length, + }); + // Combine personal and mission calendars let calendars = [...personalCalendars, ...missionCalendars]; diff --git a/app/api/events/route.ts b/app/api/events/route.ts index 212d4b5..52488d8 100644 --- a/app/api/events/route.ts +++ b/app/api/events/route.ts @@ -2,6 +2,8 @@ import { NextRequest, NextResponse } from "next/server"; import { getServerSession } from "next-auth/next"; import { authOptions } from "@/app/api/auth/options"; import { prisma } from "@/lib/prisma"; +import { updateMicrosoftEvent } from "@/lib/services/microsoft-calendar-sync"; +import { logger } from "@/lib/logger"; // Helper function to check if user can manage events in a mission calendar async function canManageMissionCalendar(userId: string, calendarId: string): Promise { @@ -130,7 +132,7 @@ export async function PUT(req: NextRequest) { ); } - // Verify calendar ownership + // Verify calendar ownership and get event with sync config const calendar = await prisma.calendar.findFirst({ where: { id: calendarId, @@ -141,6 +143,11 @@ export async function PUT(req: NextRequest) { where: { id } + }, + syncConfig: { + include: { + mailCredential: true + } } } }); @@ -169,6 +176,9 @@ export async function PUT(req: NextRequest) { } } + const existingEvent = calendar.events[0]; + + // Update event in local database const event = await prisma.event.update({ where: { id }, data: { @@ -182,6 +192,86 @@ export async function PUT(req: NextRequest) { }, }); + // If event has externalEventId and calendar has Microsoft sync, update Microsoft too + if (existingEvent.externalEventId && calendar.syncConfig && calendar.syncConfig.provider === 'microsoft' && calendar.syncConfig.syncEnabled) { + const syncConfig = calendar.syncConfig; + const mailCredential = syncConfig.mailCredential; + + if (mailCredential && mailCredential.use_oauth && mailCredential.refresh_token) { + try { + // Prepare Microsoft event data + const startDate = new Date(start); + const endDate = new Date(end); + + // Microsoft Graph API expects timezone-aware dates + const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; + + const microsoftEventData: any = { + subject: title, + }; + + if (allDay) { + // For all-day events, Microsoft uses date format + microsoftEventData.isAllDay = true; + microsoftEventData.start = { + date: startDate.toISOString().split('T')[0], + timeZone: 'UTC' + }; + microsoftEventData.end = { + date: endDate.toISOString().split('T')[0], + timeZone: 'UTC' + }; + } else { + // For timed events, use dateTime + microsoftEventData.isAllDay = false; + microsoftEventData.start = { + dateTime: startDate.toISOString(), + timeZone: timeZone + }; + microsoftEventData.end = { + dateTime: endDate.toISOString(), + timeZone: timeZone + }; + } + + if (description) { + microsoftEventData.body = { + contentType: 'HTML', + content: description + }; + } + + if (location) { + microsoftEventData.location = { + displayName: location + }; + } + + // Update Microsoft event + await updateMicrosoftEvent( + session.user.id, + mailCredential.email, + syncConfig.externalCalendarId || '', + existingEvent.externalEventId, + microsoftEventData + ); + + logger.info('Successfully synced event update to Microsoft', { + eventId: id, + externalEventId: existingEvent.externalEventId, + email: mailCredential.email, + }); + } catch (syncError: any) { + // Log error but don't fail the request - local update succeeded + logger.error('Failed to sync event update to Microsoft', { + eventId: id, + externalEventId: existingEvent.externalEventId, + error: syncError instanceof Error ? syncError.message : String(syncError), + }); + } + } + } + console.log("Updated event:", event); return NextResponse.json(event); } catch (error) { diff --git a/lib/services/microsoft-calendar-sync.ts b/lib/services/microsoft-calendar-sync.ts index 6f9bf3b..e230b7f 100644 --- a/lib/services/microsoft-calendar-sync.ts +++ b/lib/services/microsoft-calendar-sync.ts @@ -377,6 +377,77 @@ export function convertMicrosoftEventToCalDAV(microsoftEvent: MicrosoftEvent): { }; } +/** + * Update a Microsoft calendar event via Graph API + */ +export async function updateMicrosoftEvent( + userId: string, + email: string, + calendarId: string, + eventId: string, + eventData: { + subject?: string; + body?: string; + start?: { dateTime: string; timeZone: string }; + end?: { dateTime: string; timeZone: string }; + location?: { displayName: string }; + isAllDay?: boolean; + } +): Promise { + try { + const accessToken = await getMicrosoftGraphClient(userId, email); + + // Build the update payload + const payload: any = {}; + if (eventData.subject !== undefined) payload.subject = eventData.subject; + if (eventData.body !== undefined) { + payload.body = { + contentType: 'HTML', + content: eventData.body, + }; + } + if (eventData.start) payload.start = eventData.start; + if (eventData.end) payload.end = eventData.end; + if (eventData.location) payload.location = eventData.location; + if (eventData.isAllDay !== undefined) payload.isAllDay = eventData.isAllDay; + + const url = `https://graph.microsoft.com/v1.0/me/calendars/${calendarId}/events/${eventId}`; + + logger.info('Updating Microsoft event', { + userId, + email, + calendarId, + eventId, + url, + payload: JSON.stringify(payload), + }); + + await axios.patch(url, payload, { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }); + + logger.info('Successfully updated Microsoft event', { + userId, + email, + eventId, + }); + } catch (error: any) { + logger.error('Error updating Microsoft event', { + userId, + email, + calendarId, + eventId, + error: error instanceof Error ? error.message : String(error), + responseStatus: error.response?.status, + responseData: error.response?.data, + }); + throw error; + } +} + /** * Sync events from Microsoft calendar to local Prisma calendar */