Agenda refactor
This commit is contained in:
parent
6fefc74fd5
commit
f9e8409ab8
@ -63,7 +63,8 @@ export default async function CalendarPage() {
|
|||||||
{ name: { in: ["Privée", "Default"] } },
|
{ name: { in: ["Privée", "Default"] } },
|
||||||
{
|
{
|
||||||
syncConfig: {
|
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
|
// Extract mission calendars
|
||||||
const missionCalendars = missionUserRelations.flatMap(mu => mu.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
|
// Combine personal and mission calendars
|
||||||
let calendars = [...personalCalendars, ...missionCalendars];
|
let calendars = [...personalCalendars, ...missionCalendars];
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { NextRequest, NextResponse } from "next/server";
|
|||||||
import { getServerSession } from "next-auth/next";
|
import { getServerSession } from "next-auth/next";
|
||||||
import { authOptions } from "@/app/api/auth/options";
|
import { authOptions } from "@/app/api/auth/options";
|
||||||
import { prisma } from "@/lib/prisma";
|
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
|
// Helper function to check if user can manage events in a mission calendar
|
||||||
async function canManageMissionCalendar(userId: string, calendarId: string): Promise<boolean> {
|
async function canManageMissionCalendar(userId: string, calendarId: string): Promise<boolean> {
|
||||||
@ -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({
|
const calendar = await prisma.calendar.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: calendarId,
|
id: calendarId,
|
||||||
@ -141,6 +143,11 @@ export async function PUT(req: NextRequest) {
|
|||||||
where: {
|
where: {
|
||||||
id
|
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({
|
const event = await prisma.event.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
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);
|
console.log("Updated event:", event);
|
||||||
return NextResponse.json(event);
|
return NextResponse.json(event);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -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<void> {
|
||||||
|
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
|
* Sync events from Microsoft calendar to local Prisma calendar
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user