/** * GET /api/calendars/[id]/members - Mitgliederliste (nur Admin). * POST /api/calendars/[id]/members - User per E-Mail hinzufuegen (nur Admin). * DELETE /api/calendars/[id]/members - Mitglied entfernen (nur Admin). * * DB-basiertes Einladungssystem: Statt Invite-Links werden Benutzer * direkt per E-Mail-Suche ueber die Datenbank zugeordnet. Der Server * prueft die Verwaltungsberechtigung zentral (canManageCalendar). */ import { NextRequest, NextResponse } from 'next/server'; import { prisma } from '@/lib/db/client'; import { requireCalendarContext } from '@/lib/api/context'; import { addMemberSchema } from '@/lib/validation'; import { canManageCalendar } from '@/lib/permissions/permissions'; import { errorResponse, forbiddenResponse, notFoundResponse } from '@/lib/api/responses'; interface RouteParams { params: Promise<{ id: string }>; } /** Prueft Admin-Berechtigung fuer die Member-Verwaltung. */ async function requireManageContext(calendarId: string) { const context = await requireCalendarContext(calendarId); if (!canManageCalendar(context.user, context.calendar)) { return null; } return context; } export async function GET( _request: NextRequest, { params }: RouteParams, ): Promise { try { const { id } = await params; const context = await requireManageContext(id); if (!context) { return forbiddenResponse(); } const members = await prisma.calendarMember.findMany({ where: { calendarId: id }, orderBy: { createdAt: 'asc' }, select: { id: true, createdAt: true, user: { select: { id: true, email: true, username: true } }, }, }); return NextResponse.json({ members }); } catch (error) { return errorResponse(error); } } export async function POST( request: NextRequest, { params }: RouteParams, ): Promise { try { const { id } = await params; const context = await requireManageContext(id); if (!context) { return forbiddenResponse(); } const body = addMemberSchema.parse(await request.json()); const targetUser = await prisma.user.findUnique({ where: { email: body.email }, select: { id: true, email: true }, }); if (!targetUser) { return notFoundResponse( 'Kein Benutzer mit dieser E-Mail gefunden. Der Benutzer muss sich zuerst registrieren.', ); } // Ein User gehoert genau einem Kalender an (unique auf user_id). const existingMembership = await prisma.calendarMember.findUnique({ where: { userId: targetUser.id }, select: { calendarId: true }, }); if (existingMembership) { return NextResponse.json( { error: existingMembership.calendarId === id ? 'Dieser Benutzer ist bereits Mitglied dieses Kalenders.' : 'Dieser Benutzer ist bereits einem anderen Kalender zugeordnet.', }, { status: 409 }, ); } const member = await prisma.calendarMember.create({ data: { calendarId: id, userId: targetUser.id }, select: { id: true, createdAt: true, user: { select: { id: true, email: true } }, }, }); return NextResponse.json({ member }, { status: 201 }); } catch (error) { return errorResponse(error); } } export async function DELETE( request: NextRequest, { params }: RouteParams, ): Promise { try { const { id } = await params; const context = await requireManageContext(id); if (!context) { return forbiddenResponse(); } const memberId = request.nextUrl.searchParams.get('memberId'); if (!memberId) { return NextResponse.json( { error: 'memberId ist erforderlich.' }, { status: 400 }, ); } const member = await prisma.calendarMember.findUnique({ where: { id: memberId }, select: { id: true, calendarId: true }, }); if (!member || member.calendarId !== id) { return notFoundResponse('Mitglied nicht gefunden.'); } await prisma.calendarMember.delete({ where: { id: memberId } }); return NextResponse.json({ ok: true }); } catch (error) { return errorResponse(error); } }