fix: add tournament update API endpoint and consolidate delete into main tournaments API
This commit is contained in:
@@ -1,108 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { canDeleteTournament } from "@/lib/permissions";
|
||||
|
||||
/**
|
||||
* DELETE /api/admin/tournaments/[id]
|
||||
*
|
||||
* Delete a tournament and optionally its associated matches
|
||||
* 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: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const tournamentId = parseInt(params.id);
|
||||
const body = await request.json();
|
||||
const { deleteMatches = false } = body;
|
||||
|
||||
// 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 }
|
||||
);
|
||||
}
|
||||
|
||||
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 teams
|
||||
await prisma.team.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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { canManageTournament, canDeleteTournament } from "@/lib/permissions";
|
||||
import { getTournamentStatus } from "@/lib/tournamentUtils";
|
||||
|
||||
interface RouteParams {
|
||||
params: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/tournaments/[id]
|
||||
*
|
||||
* Get a single tournament by ID
|
||||
*/
|
||||
export async function GET(request: Request, { params }: RouteParams) {
|
||||
try {
|
||||
const tournamentId = parseInt(params.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,
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
include: {
|
||||
player1: true,
|
||||
player2: true,
|
||||
},
|
||||
},
|
||||
rounds: {
|
||||
include: {
|
||||
bracketMatchups: {
|
||||
include: {
|
||||
team1: {
|
||||
include: {
|
||||
player1: true,
|
||||
player2: true,
|
||||
},
|
||||
},
|
||||
team2: {
|
||||
include: {
|
||||
player1: true,
|
||||
player2: 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 }: RouteParams) {
|
||||
try {
|
||||
const tournamentId = parseInt(params.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,
|
||||
format,
|
||||
status,
|
||||
maxParticipants,
|
||||
ownerId,
|
||||
} = body;
|
||||
|
||||
// Validate required fields
|
||||
if (!name || typeof name !== 'string' || name.trim().length === 0) {
|
||||
return NextResponse.json(
|
||||
{ error: "Tournament name is required" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Prepare update data
|
||||
const updateData: Record<string, unknown> = {
|
||||
name: name.trim(),
|
||||
description: description || null,
|
||||
eventDate: eventDate ? new Date(eventDate) : null,
|
||||
eventType: eventType || "tournament",
|
||||
format: format || "round_robin",
|
||||
maxParticipants: maxParticipants ? parseInt(maxParticipants) : null,
|
||||
};
|
||||
|
||||
// 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 }: RouteParams) {
|
||||
try {
|
||||
const tournamentId = parseInt(params.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 teams
|
||||
await prisma.team.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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user