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 { logger } from "@/lib/logger"; async function getAdminToken() { try { const tokenResponse = await fetch( `${process.env.KEYCLOAK_BASE_URL}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ grant_type: 'client_credentials', client_id: process.env.KEYCLOAK_CLIENT_ID!, client_secret: process.env.KEYCLOAK_CLIENT_SECRET!, }), } ); const data = await tokenResponse.json(); if (!tokenResponse.ok || !data.access_token) { return null; } return data.access_token; } catch (error) { console.error('Token Error:', error); return null; } } async function getAllKeycloakGroups(token: string): Promise> { try { const response = await fetch( `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/groups`, { headers: { Authorization: `Bearer ${token}`, }, } ); if (!response.ok) { throw new Error('Failed to fetch groups from Keycloak'); } const groups = await response.json(); const groupNames = new Set(); groups.forEach((group: any) => { groupNames.add(group.name); }); return groupNames; } catch (error) { console.error('Error fetching groups:', error); return new Set(); } } /** * GET /api/admin/clean-calendars * Lists orphan group calendars (calendars whose groups no longer exist in Keycloak) * * Query params: * - action=list (default): List orphan calendars * - action=delete: Delete orphan calendars */ export async function GET(req: NextRequest) { const session = await getServerSession(authOptions); // Security: only authenticated users can access this if (!session?.user?.id) { return NextResponse.json({ error: "Non authentifié" }, { status: 401 }); } try { const { searchParams } = new URL(req.url); const action = searchParams.get('action') || 'list'; // Get Keycloak token const token = await getAdminToken(); if (!token) { return NextResponse.json({ error: "Erreur d'authentification Keycloak" }, { status: 500 }); } // Get all existing groups from Keycloak const keycloakGroups = await getAllKeycloakGroups(token); // Get all group calendars from database const groupCalendars = await prisma.calendar.findMany({ where: { name: { startsWith: 'Groupe: ', }, }, include: { _count: { select: { events: true, }, }, }, }); // Identify orphan calendars const orphanCalendars = groupCalendars.filter((calendar) => { const groupName = calendar.name.replace('Groupe: ', ''); return !keycloakGroups.has(groupName); }); const validCalendars = groupCalendars.filter((calendar) => { const groupName = calendar.name.replace('Groupe: ', ''); return keycloakGroups.has(groupName); }); if (action === 'list') { // Just list the orphan calendars return NextResponse.json({ summary: { totalGroupCalendars: groupCalendars.length, validCalendars: validCalendars.length, orphanCalendars: orphanCalendars.length, keycloakGroups: keycloakGroups.size, }, orphans: orphanCalendars.map(cal => ({ id: cal.id, name: cal.name, color: cal.color, createdAt: cal.createdAt, eventsCount: cal._count.events, })), validGroups: validCalendars.map(cal => ({ id: cal.id, name: cal.name, color: cal.color, eventsCount: cal._count.events, })), }); } else if (action === 'delete') { // Delete orphan calendars if (orphanCalendars.length === 0) { return NextResponse.json({ message: "Aucun calendrier orphelin à supprimer", deleted: 0, }); } const deletedIds: string[] = []; const errors: Array<{ calendarId: string; calendarName: string; error: string }> = []; for (const calendar of orphanCalendars) { try { await prisma.calendar.delete({ where: { id: calendar.id }, }); deletedIds.push(calendar.id); logger.info('Orphan calendar deleted', { calendarId: calendar.id, calendarName: calendar.name }); } catch (error) { logger.error('Failed to delete orphan calendar', { calendarId: calendar.id, error }); errors.push({ calendarId: calendar.id, calendarName: calendar.name, error: error instanceof Error ? error.message : String(error), }); } } return NextResponse.json({ message: `${deletedIds.length} calendrier(s) orphelin(s) supprimé(s)`, deleted: deletedIds.length, deletedCalendars: orphanCalendars .filter(cal => deletedIds.includes(cal.id)) .map(cal => cal.name), errors: errors.length > 0 ? errors : undefined, }); } else { return NextResponse.json( { error: "Action invalide. Utilisez 'list' ou 'delete'" }, { status: 400 } ); } } catch (error) { logger.error('Error in clean-calendars API', { error }); return NextResponse.json( { error: "Erreur serveur", details: error instanceof Error ? error.message : String(error) }, { status: 500 } ); } }