Agenda refactor

This commit is contained in:
alma 2026-01-15 14:54:30 +01:00
parent 95a56d8828
commit a4c4baa491
4 changed files with 103 additions and 4 deletions

View File

@ -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 { deleteMicrosoftEvent } 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<boolean> {
@ -45,10 +47,20 @@ export async function DELETE(req: NextRequest, props: { params: Promise<{ id: st
}
try {
// First, find the event and its associated calendar
// First, find the event and its associated calendar with sync config
const event = await prisma.event.findUnique({
where: { id: params.id },
include: { calendar: true },
include: {
calendar: {
include: {
syncConfig: {
include: {
mailCredential: true
}
}
}
}
},
});
if (!event) {
@ -77,7 +89,41 @@ export async function DELETE(req: NextRequest, props: { params: Promise<{ id: st
}
}
// Delete the event
// If event has externalEventId and calendar has Microsoft sync, delete from Microsoft too
if (event.externalEventId && event.calendar.syncConfig && event.calendar.syncConfig.provider === 'microsoft' && event.calendar.syncConfig.syncEnabled) {
const syncConfig = event.calendar.syncConfig;
const mailCredential = syncConfig.mailCredential;
if (mailCredential && mailCredential.use_oauth && mailCredential.refresh_token) {
try {
await deleteMicrosoftEvent(
session.user.id,
mailCredential.email,
syncConfig.externalCalendarId || '',
event.externalEventId
);
logger.info('Successfully synced event deletion to Microsoft', {
eventId: params.id,
externalEventId: event.externalEventId,
email: mailCredential.email,
});
} catch (syncError: any) {
// Log error but don't fail the request - local deletion will proceed
// Don't disable syncConfig for permission errors (403) - user just needs to re-authenticate
const isPermissionError = syncError.response?.status === 403;
logger.error('Failed to sync event deletion to Microsoft', {
eventId: params.id,
externalEventId: event.externalEventId,
error: syncError instanceof Error ? syncError.message : String(syncError),
isPermissionError,
suggestion: isPermissionError ? 'User needs to re-authenticate with Calendars.ReadWrite scope' : undefined,
});
}
}
}
// Delete the event from local database
await prisma.event.delete({
where: { id: params.id },
});

View File

@ -2,7 +2,7 @@ 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 { updateMicrosoftEvent, deleteMicrosoftEvent } from "@/lib/services/microsoft-calendar-sync";
import { logger } from "@/lib/logger";
// Helper function to check if user can manage events in a mission calendar
@ -263,10 +263,14 @@ export async function PUT(req: NextRequest) {
});
} catch (syncError: any) {
// Log error but don't fail the request - local update succeeded
// Don't disable syncConfig for permission errors (403) - user just needs to re-authenticate
const isPermissionError = syncError.response?.status === 403;
logger.error('Failed to sync event update to Microsoft', {
eventId: id,
externalEventId: existingEvent.externalEventId,
error: syncError instanceof Error ? syncError.message : String(syncError),
isPermissionError,
suggestion: isPermissionError ? 'User needs to re-authenticate with Calendars.ReadWrite scope' : undefined,
});
}
}

View File

@ -448,6 +448,54 @@ export async function updateMicrosoftEvent(
}
}
/**
* Delete a Microsoft calendar event via Graph API
*/
export async function deleteMicrosoftEvent(
userId: string,
email: string,
calendarId: string,
eventId: string
): Promise<void> {
try {
const accessToken = await getMicrosoftGraphClient(userId, email);
const url = `https://graph.microsoft.com/v1.0/me/calendars/${calendarId}/events/${eventId}`;
logger.info('Deleting Microsoft event', {
userId,
email,
calendarId,
eventId,
url,
});
await axios.delete(url, {
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
});
logger.info('Successfully deleted Microsoft event', {
userId,
email,
eventId,
});
} catch (error: any) {
logger.error('Error deleting 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
*/

View File

@ -21,6 +21,7 @@ const REQUIRED_SCOPES = [
'https://graph.microsoft.com/Mail.Read', // Read mail via Graph API
'https://graph.microsoft.com/Mail.Send', // Send mail via Graph API
'https://graph.microsoft.com/Calendars.Read', // Read calendars via Graph API
'https://graph.microsoft.com/Calendars.ReadWrite', // Read and write calendars via Graph API (for updates/deletes)
].join(' ');
/**