agenda finition
This commit is contained in:
parent
77d96cedac
commit
d6c8376bb4
185
app/api/calendars/[calendarId]/route.ts
Normal file
185
app/api/calendars/[calendarId]/route.ts
Normal file
@ -0,0 +1,185 @@
|
||||
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";
|
||||
|
||||
/**
|
||||
* GET /api/calendars/[calendarId]
|
||||
* Retrieves a specific calendar
|
||||
*/
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ calendarId: string }> }
|
||||
) {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { calendarId } = await params;
|
||||
|
||||
const calendar = await prisma.calendar.findUnique({
|
||||
where: { id: calendarId },
|
||||
include: {
|
||||
events: {
|
||||
orderBy: {
|
||||
start: 'asc'
|
||||
}
|
||||
},
|
||||
mission: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!calendar) {
|
||||
return NextResponse.json({ error: "Calendrier non trouvé" }, { status: 404 });
|
||||
}
|
||||
|
||||
// Check if user has access to this calendar
|
||||
const hasAccess =
|
||||
calendar.userId === session.user.id || // User owns the calendar
|
||||
calendar.isPublic || // Calendar is public
|
||||
(calendar.mission && calendar.mission.creatorId === session.user.id); // User created the mission
|
||||
|
||||
if (!hasAccess) {
|
||||
return NextResponse.json({ error: "Non autorisé" }, { status: 403 });
|
||||
}
|
||||
|
||||
return NextResponse.json(calendar);
|
||||
} catch (error) {
|
||||
logger.error('Error fetching calendar', { error });
|
||||
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /api/calendars/[calendarId]
|
||||
* Deletes a calendar and all its events
|
||||
*/
|
||||
export async function DELETE(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ calendarId: string }> }
|
||||
) {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { calendarId } = await params;
|
||||
|
||||
const calendar = await prisma.calendar.findUnique({
|
||||
where: { id: calendarId },
|
||||
include: {
|
||||
mission: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!calendar) {
|
||||
return NextResponse.json({ error: "Calendrier non trouvé" }, { status: 404 });
|
||||
}
|
||||
|
||||
// Check permissions
|
||||
// Only allow deletion if:
|
||||
// 1. User owns the calendar
|
||||
// 2. OR it's a group calendar (starts with "Groupe:")
|
||||
// 3. OR user created the associated mission
|
||||
const isOwner = calendar.userId === session.user.id;
|
||||
const isGroupCalendar = calendar.name.startsWith("Groupe:");
|
||||
const isMissionCreator = calendar.mission && calendar.mission.creatorId === session.user.id;
|
||||
|
||||
if (!isOwner && !isGroupCalendar && !isMissionCreator) {
|
||||
return NextResponse.json(
|
||||
{ error: "Vous n'avez pas la permission de supprimer ce calendrier" },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
// For group calendars, verify the group no longer exists
|
||||
if (isGroupCalendar) {
|
||||
logger.info('Deleting group calendar', {
|
||||
calendarId,
|
||||
calendarName: calendar.name,
|
||||
userId: session.user.id
|
||||
});
|
||||
}
|
||||
|
||||
// Delete the calendar (cascade will delete events)
|
||||
await prisma.calendar.delete({
|
||||
where: { id: calendarId },
|
||||
});
|
||||
|
||||
logger.info('Calendar deleted successfully', {
|
||||
calendarId,
|
||||
calendarName: calendar.name
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true, message: "Calendrier supprimé avec succès" });
|
||||
} catch (error) {
|
||||
logger.error('Error deleting calendar', { error });
|
||||
return NextResponse.json({ error: "Erreur lors de la suppression du calendrier" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH /api/calendars/[calendarId]
|
||||
* Updates calendar properties (name, color, description, etc.)
|
||||
*/
|
||||
export async function PATCH(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ calendarId: string }> }
|
||||
) {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { calendarId } = await params;
|
||||
const body = await req.json();
|
||||
|
||||
const calendar = await prisma.calendar.findUnique({
|
||||
where: { id: calendarId },
|
||||
include: {
|
||||
mission: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!calendar) {
|
||||
return NextResponse.json({ error: "Calendrier non trouvé" }, { status: 404 });
|
||||
}
|
||||
|
||||
// Check permissions
|
||||
const isOwner = calendar.userId === session.user.id;
|
||||
const isMissionCreator = calendar.mission && calendar.mission.creatorId === session.user.id;
|
||||
|
||||
if (!isOwner && !isMissionCreator) {
|
||||
return NextResponse.json(
|
||||
{ error: "Vous n'avez pas la permission de modifier ce calendrier" },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
// Update calendar
|
||||
const updatedCalendar = await prisma.calendar.update({
|
||||
where: { id: calendarId },
|
||||
data: {
|
||||
...(body.name && { name: body.name }),
|
||||
...(body.color && { color: body.color }),
|
||||
...(body.description !== undefined && { description: body.description }),
|
||||
...(body.isPublic !== undefined && { isPublic: body.isPublic }),
|
||||
},
|
||||
});
|
||||
|
||||
logger.info('Calendar updated successfully', {
|
||||
calendarId,
|
||||
updatedFields: Object.keys(body)
|
||||
});
|
||||
|
||||
return NextResponse.json(updatedCalendar);
|
||||
} catch (error) {
|
||||
logger.error('Error updating calendar', { error });
|
||||
return NextResponse.json({ error: "Erreur lors de la mise à jour du calendrier" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@ -277,6 +277,11 @@ export default function VisionPage() {
|
||||
const groupsRes = await fetch(`/api/users/${userId}/groups`);
|
||||
if (groupsRes.ok) {
|
||||
const groupsData = await groupsRes.json();
|
||||
console.log('[Vision] Groups loaded:', groupsData.map((g: any) => ({
|
||||
id: g.id,
|
||||
name: g.name,
|
||||
calendarColor: g.calendarColor
|
||||
})));
|
||||
setGroups(Array.isArray(groupsData) ? groupsData : []);
|
||||
} else {
|
||||
console.error("Failed to fetch groups");
|
||||
|
||||
149
scripts/clean-orphan-calendars.ts
Normal file
149
scripts/clean-orphan-calendars.ts
Normal file
@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Script de nettoyage des calendriers de groupes orphelins
|
||||
*
|
||||
* Ce script supprime les calendriers de groupes dont le groupe n'existe plus dans Keycloak.
|
||||
*
|
||||
* Usage:
|
||||
* npx tsx scripts/clean-orphan-calendars.ts
|
||||
*/
|
||||
|
||||
import { prisma } from '../lib/prisma';
|
||||
|
||||
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) {
|
||||
console.error('❌ Erreur token:', data);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.access_token;
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur token:', 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('❌ Erreur lors de la récupération des groupes:', error);
|
||||
return new Set();
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanOrphanCalendars() {
|
||||
console.log('🧹 Début du nettoyage des calendriers orphelins...\n');
|
||||
|
||||
// 1. Récupérer le token Keycloak
|
||||
const token = await getAdminToken();
|
||||
if (!token) {
|
||||
console.error('❌ Impossible d\'obtenir le token Keycloak');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 2. Récupérer tous les groupes existants dans Keycloak
|
||||
console.log('📋 Récupération des groupes depuis Keycloak...');
|
||||
const keycloakGroups = await getAllKeycloakGroups(token);
|
||||
console.log(`✅ ${keycloakGroups.size} groupes trouvés dans Keycloak\n`);
|
||||
|
||||
// 3. Récupérer tous les calendriers de groupes
|
||||
console.log('📋 Récupération des calendriers de groupes depuis la base...');
|
||||
const groupCalendars = await prisma.calendar.findMany({
|
||||
where: {
|
||||
name: {
|
||||
startsWith: 'Groupe: ',
|
||||
},
|
||||
},
|
||||
});
|
||||
console.log(`✅ ${groupCalendars.length} calendriers de groupes trouvés\n`);
|
||||
|
||||
// 4. Identifier les calendriers orphelins
|
||||
const orphanCalendars = groupCalendars.filter((calendar) => {
|
||||
const groupName = calendar.name.replace('Groupe: ', '');
|
||||
return !keycloakGroups.has(groupName);
|
||||
});
|
||||
|
||||
if (orphanCalendars.length === 0) {
|
||||
console.log('✅ Aucun calendrier orphelin trouvé. Base de données propre!\n');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`⚠️ ${orphanCalendars.length} calendrier(s) orphelin(s) trouvé(s):\n`);
|
||||
orphanCalendars.forEach((cal) => {
|
||||
console.log(` - ${cal.name} (ID: ${cal.id})`);
|
||||
});
|
||||
|
||||
// 5. Demander confirmation (commentez cette section si vous voulez une suppression automatique)
|
||||
console.log('\n⚠️ ATTENTION: Les calendriers ci-dessus vont être supprimés avec tous leurs événements!');
|
||||
console.log('Pour confirmer, lancez le script avec: npx tsx scripts/clean-orphan-calendars.ts --confirm\n');
|
||||
|
||||
const isConfirmed = process.argv.includes('--confirm');
|
||||
|
||||
if (!isConfirmed) {
|
||||
console.log('❌ Suppression annulée. Utilisez --confirm pour supprimer.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 6. Supprimer les calendriers orphelins
|
||||
console.log('\n🗑️ Suppression des calendriers orphelins...\n');
|
||||
|
||||
for (const calendar of orphanCalendars) {
|
||||
try {
|
||||
await prisma.calendar.delete({
|
||||
where: { id: calendar.id },
|
||||
});
|
||||
console.log(` ✅ Supprimé: ${calendar.name}`);
|
||||
} catch (error) {
|
||||
console.error(` ❌ Erreur lors de la suppression de ${calendar.name}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n✅ Nettoyage terminé! ${orphanCalendars.length} calendrier(s) supprimé(s).\n`);
|
||||
}
|
||||
|
||||
// Exécuter le script
|
||||
cleanOrphanCalendars()
|
||||
.then(() => {
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ Erreur fatale:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user