agenda finition

This commit is contained in:
alma 2026-01-21 00:39:05 +01:00
parent d6c8376bb4
commit f5a8f3de45
2 changed files with 234 additions and 0 deletions

View File

@ -0,0 +1,203 @@
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 "@/utils/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 }
);
}
}

View File

@ -0,0 +1,31 @@
-- Script SQL pour lister et supprimer les calendriers de groupes orphelins
--
-- Usage:
-- 1. Se connecter à PostgreSQL
-- 2. Copier-coller les requêtes une par une
-- 1. LISTER tous les calendriers de groupes
SELECT
id,
name,
color,
"createdAt",
"userId"
FROM "Calendar"
WHERE name LIKE 'Groupe:%'
ORDER BY "createdAt" DESC;
-- 2. COMPTER les calendriers de groupes
SELECT COUNT(*) as total_group_calendars
FROM "Calendar"
WHERE name LIKE 'Groupe:%';
-- 3. SUPPRIMER un calendrier spécifique (remplacez CALENDAR_ID)
-- DELETE FROM "Calendar" WHERE id = 'CALENDAR_ID';
-- 4. SUPPRIMER tous les calendriers d'un groupe spécifique (remplacez le nom)
-- DELETE FROM "Calendar" WHERE name = 'Groupe: NomDuGroupe';
-- 5. EXEMPLE: Supprimer tous les calendriers de groupes (ATTENTION!)
-- Décommentez cette ligne seulement si vous êtes SÛR de vouloir tout supprimer
-- DELETE FROM "Calendar" WHERE name LIKE 'Groupe:%';