import { NextResponse } from "next/server"; import { prisma } from "@/lib/prisma"; import { canManageTournament, canDeleteTournament } from "@/lib/permissions"; import { getTournamentStatus } from "@/lib/tournamentUtils"; /** * GET /api/tournaments/[id] * * Get a single tournament by ID */ export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) { try { const { id } = await params const tournamentId = parseInt(id); // Validate tournament ID if (isNaN(tournamentId)) { return NextResponse.json( { error: "Invalid tournament ID" }, { status: 400 } ); } // Check if user can view/manage this tournament const permission = await canManageTournament(tournamentId); if (!permission.allowed) { return NextResponse.json( { error: permission.reason || "Not authorized to view this tournament" }, { status: 403 } ); } // Fetch the tournament const tournament = await prisma.event.findUnique({ where: { id: tournamentId }, include: { participants: { include: { player: true, }, }, rounds: { include: { bracketMatchups: { include: { player1P1: true, player1P2: true, player2P1: true, player2P2: true, match: true, }, }, }, }, }, }); if (!tournament) { return NextResponse.json( { error: "Tournament not found" }, { status: 404 } ); } // Update tournament status if needed const calculatedStatus = getTournamentStatus(tournament.eventDate); if (tournament.status !== calculatedStatus) { tournament.status = calculatedStatus; } return NextResponse.json({ tournament }); } catch (error: unknown) { console.error("Error fetching tournament:", error); const message = error instanceof Error ? error.message : "Failed to fetch tournament"; return NextResponse.json( { error: message }, { status: 500 } ); } } /** * PUT /api/tournaments/[id] * * Update a tournament by ID */ export async function PUT(request: Request, { params }: { params: Promise<{ id: string }> }) { try { const { id } = await params const tournamentId = parseInt(id); // Validate tournament ID if (isNaN(tournamentId)) { return NextResponse.json( { error: "Invalid tournament ID" }, { status: 400 } ); } // Check if user can manage this tournament const permission = await canManageTournament(tournamentId); if (!permission.allowed) { return NextResponse.json( { error: permission.reason || "Not authorized to update this tournament" }, { status: 403 } ); } // Verify tournament exists const existingTournament = await prisma.event.findUnique({ where: { id: tournamentId }, }); if (!existingTournament) { return NextResponse.json( { error: "Tournament not found" }, { status: 404 } ); } const body = await request.json(); const { name, description, eventDate, eventType, tournamentType, format, status, maxParticipants, ownerId, targetScore, allowTies, teamDurability, partnerRotation, allowByes, } = body; // Validate name only if it's being updated if (body.hasOwnProperty('name')) { if (!name || typeof name !== 'string' || name.trim().length === 0) { return NextResponse.json( { error: "Tournament name is required" }, { status: 400 } ); } } // Prepare update data - only include fields that are present in the request const updateData: Record = {}; if (body.hasOwnProperty('name')) updateData.name = name.trim(); if (body.hasOwnProperty('description')) updateData.description = description || null; if (body.hasOwnProperty('eventDate')) updateData.eventDate = eventDate ? new Date(eventDate) : null; if (body.hasOwnProperty('eventType')) updateData.eventType = eventType || "tournament"; if (body.hasOwnProperty('tournamentType')) updateData.tournamentType = tournamentType || "individual"; if (body.hasOwnProperty('format')) updateData.format = format || "round_robin"; if (body.hasOwnProperty('maxParticipants')) updateData.maxParticipants = maxParticipants ? parseInt(maxParticipants) : null; if (body.hasOwnProperty('targetScore')) updateData.targetScore = targetScore ? parseInt(targetScore) : null; if (body.hasOwnProperty('allowTies')) updateData.allowTies = allowTies ?? false; if (body.hasOwnProperty('teamDurability')) updateData.teamDurability = teamDurability || "permanent"; if (body.hasOwnProperty('partnerRotation')) updateData.partnerRotation = partnerRotation || "none"; if (body.hasOwnProperty('allowByes')) updateData.allowByes = allowByes ?? true; // Only allow status updates if they don't conflict with auto-calculation if (status) { const calculatedStatus = getTournamentStatus(eventDate ? new Date(eventDate) : existingTournament.eventDate); if (status !== calculatedStatus) { // Allow manual status override but log it console.log(`Manual status override: ${existingTournament.status} -> ${status} (calculated: ${calculatedStatus})`); } updateData.status = status; } // Update owner if specified (and user has permission to reassign ownership) if (ownerId !== undefined) { // Only site_admin or club_admin can reassign ownership const canReassign = await canDeleteTournament(tournamentId); if (canReassign.allowed) { updateData.ownerId = ownerId || null; } } // Update the tournament const updatedTournament = await prisma.event.update({ where: { id: tournamentId }, data: updateData, }); return NextResponse.json({ success: true, tournament: updatedTournament, }); } catch (error: unknown) { console.error("Error updating tournament:", error); const message = error instanceof Error ? error.message : "Failed to update tournament"; return NextResponse.json( { error: message }, { status: 500 } ); } } /** * DELETE /api/tournaments/[id] * * Delete a tournament by ID * Options: * - deleteMatches: Delete all matches associated with the tournament * - orphanMatches: Keep matches but remove tournament association (eventId becomes null) */ export async function DELETE(request: Request, { params }: { params: Promise<{ id: string }> }) { try { const { id } = await params const tournamentId = parseInt(id); // Validate tournament ID if (isNaN(tournamentId)) { return NextResponse.json( { error: "Invalid tournament ID" }, { status: 400 } ); } // Check if user has permission to delete this tournament const permission = await canDeleteTournament(tournamentId); if (!permission.allowed) { return NextResponse.json( { error: permission.reason || "Not authorized to delete this tournament" }, { status: 403 } ); } // Verify tournament exists const tournament = await prisma.event.findUnique({ where: { id: tournamentId }, include: { matches: true }, }); if (!tournament) { return NextResponse.json( { error: "Tournament not found" }, { status: 404 } ); } // Parse request body to get delete options let deleteMatches = false; try { const body = await request.json(); deleteMatches = body.deleteMatches || false; } catch { // If no body is provided, default to orphaning matches deleteMatches = false; } const matchCount = tournament.matches.length; // Handle matches based on user choice if (deleteMatches) { // Delete all matches associated with the tournament await prisma.match.deleteMany({ where: { eventId: tournamentId }, }); } else { // Orphan matches - remove tournament association await prisma.match.updateMany({ where: { eventId: tournamentId }, data: { eventId: null }, }); } // Delete event participants await prisma.eventParticipant.deleteMany({ where: { eventId: tournamentId }, }); // Delete tournament rounds await prisma.tournamentRound.deleteMany({ where: { eventId: tournamentId }, }); // Delete bracket matchups await prisma.bracketMatchup.deleteMany({ where: { eventId: tournamentId }, }); // Delete the tournament itself await prisma.event.delete({ where: { id: tournamentId }, }); return NextResponse.json({ success: true, message: "Tournament deleted successfully", deletedTournament: tournament.name, deletedMatches: deleteMatches ? matchCount : 0, orphanedMatches: deleteMatches ? 0 : matchCount, }); } catch (error: unknown) { console.error("Error deleting tournament:", error); const message = error instanceof Error ? error.message : "Failed to delete tournament"; return NextResponse.json( { error: message }, { status: 500 } ); } }