agenda finition
This commit is contained in:
parent
d6c8376bb4
commit
f5a8f3de45
203
app/api/admin/clean-calendars/route.ts
Normal file
203
app/api/admin/clean-calendars/route.ts
Normal 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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
31
scripts/list-group-calendars.sql
Normal file
31
scripts/list-group-calendars.sql
Normal 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:%';
|
||||
Loading…
Reference in New Issue
Block a user