204 lines
5.8 KiB
TypeScript
204 lines
5.8 KiB
TypeScript
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<Set<string>> {
|
|
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<string>();
|
|
|
|
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 }
|
|
);
|
|
}
|
|
}
|