Create comprehensive tournament management API with: - GET/POST /api/tournaments for listing and creating tournaments - GET/PUT/DELETE /api/tournaments/[id] for tournament CRUD operations - GET/POST /api/tournaments/[id]/inscriptions for team registration - PUT/DELETE /api/tournaments/[id]/inscriptions/[inscriptionId] for inscription management - POST /api/tournaments/[id]/generate-bracket for single elimination bracket generation - GET/PUT /api/tournaments/[id]/matches/[matchId] for match results and automatic winner advancement Features bracket generation with proper seeding, bye handling for non-power-of-2 team counts, and automatic winner advancement to next round matches. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
300 lines
7.2 KiB
TypeScript
300 lines
7.2 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { getServerSession } from 'next-auth';
|
|
import { authOptions } from '@/lib/auth';
|
|
import { db } from '@/lib/db';
|
|
import { z } from 'zod';
|
|
|
|
interface RouteContext {
|
|
params: Promise<{ id: string }>;
|
|
}
|
|
|
|
// Validation schema for creating an inscription
|
|
const createInscriptionSchema = z.object({
|
|
player1Id: z.string().cuid('Invalid player 1 ID'),
|
|
player2Id: z.string().cuid('Invalid player 2 ID').optional(),
|
|
teamName: z.string().max(100).optional(),
|
|
});
|
|
|
|
// GET /api/tournaments/[id]/inscriptions - List inscriptions for tournament
|
|
export async function GET(
|
|
request: NextRequest,
|
|
context: RouteContext
|
|
) {
|
|
try {
|
|
const session = await getServerSession(authOptions);
|
|
|
|
if (!session?.user) {
|
|
return NextResponse.json(
|
|
{ error: 'Unauthorized' },
|
|
{ status: 401 }
|
|
);
|
|
}
|
|
|
|
const { id } = await context.params;
|
|
|
|
// Verify tournament exists and belongs to user's organization
|
|
const tournament = await db.tournament.findFirst({
|
|
where: {
|
|
id,
|
|
organizationId: session.user.organizationId,
|
|
},
|
|
});
|
|
|
|
if (!tournament) {
|
|
return NextResponse.json(
|
|
{ error: 'Tournament not found' },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
const inscriptions = await db.tournamentInscription.findMany({
|
|
where: {
|
|
tournamentId: id,
|
|
},
|
|
include: {
|
|
client: {
|
|
select: {
|
|
id: true,
|
|
firstName: true,
|
|
lastName: true,
|
|
email: true,
|
|
phone: true,
|
|
level: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: {
|
|
registeredAt: 'asc',
|
|
},
|
|
});
|
|
|
|
// Enrich inscriptions with partner info if partnerId exists
|
|
const enrichedInscriptions = await Promise.all(
|
|
inscriptions.map(async (inscription) => {
|
|
let partner = null;
|
|
if (inscription.partnerId) {
|
|
partner = await db.client.findUnique({
|
|
where: { id: inscription.partnerId },
|
|
select: {
|
|
id: true,
|
|
firstName: true,
|
|
lastName: true,
|
|
email: true,
|
|
phone: true,
|
|
level: true,
|
|
},
|
|
});
|
|
}
|
|
return {
|
|
...inscription,
|
|
partner,
|
|
};
|
|
})
|
|
);
|
|
|
|
return NextResponse.json(enrichedInscriptions);
|
|
} catch (error) {
|
|
console.error('Error fetching inscriptions:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to fetch inscriptions' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// POST /api/tournaments/[id]/inscriptions - Register team/player
|
|
export async function POST(
|
|
request: NextRequest,
|
|
context: RouteContext
|
|
) {
|
|
try {
|
|
const session = await getServerSession(authOptions);
|
|
|
|
if (!session?.user) {
|
|
return NextResponse.json(
|
|
{ error: 'Unauthorized' },
|
|
{ status: 401 }
|
|
);
|
|
}
|
|
|
|
const { id } = await context.params;
|
|
|
|
// Verify tournament exists and belongs to user's organization
|
|
const tournament = await db.tournament.findFirst({
|
|
where: {
|
|
id,
|
|
organizationId: session.user.organizationId,
|
|
},
|
|
include: {
|
|
_count: {
|
|
select: {
|
|
inscriptions: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!tournament) {
|
|
return NextResponse.json(
|
|
{ error: 'Tournament not found' },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
// Check tournament status is OPEN (REGISTRATION_OPEN)
|
|
if (tournament.status !== 'REGISTRATION_OPEN') {
|
|
return NextResponse.json(
|
|
{ error: 'Tournament is not open for registration' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Check maxTeams not exceeded
|
|
if (tournament.maxPlayers && tournament._count.inscriptions >= tournament.maxPlayers) {
|
|
return NextResponse.json(
|
|
{ error: 'Tournament has reached maximum number of teams' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const body = await request.json();
|
|
|
|
// Validate input
|
|
const validationResult = createInscriptionSchema.safeParse(body);
|
|
if (!validationResult.success) {
|
|
return NextResponse.json(
|
|
{
|
|
error: 'Invalid inscription data',
|
|
details: validationResult.error.flatten().fieldErrors,
|
|
},
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const { player1Id, player2Id, teamName } = validationResult.data;
|
|
|
|
// Verify player 1 exists and belongs to user's organization
|
|
const player1 = await db.client.findFirst({
|
|
where: {
|
|
id: player1Id,
|
|
organizationId: session.user.organizationId,
|
|
},
|
|
});
|
|
|
|
if (!player1) {
|
|
return NextResponse.json(
|
|
{ error: 'Player 1 not found or does not belong to your organization' },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
// Check player 1 is not already registered
|
|
const existingInscription = await db.tournamentInscription.findFirst({
|
|
where: {
|
|
tournamentId: id,
|
|
OR: [
|
|
{ clientId: player1Id },
|
|
{ partnerId: player1Id },
|
|
],
|
|
},
|
|
});
|
|
|
|
if (existingInscription) {
|
|
return NextResponse.json(
|
|
{ error: 'Player 1 is already registered for this tournament' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Verify player 2 if provided
|
|
if (player2Id) {
|
|
const player2 = await db.client.findFirst({
|
|
where: {
|
|
id: player2Id,
|
|
organizationId: session.user.organizationId,
|
|
},
|
|
});
|
|
|
|
if (!player2) {
|
|
return NextResponse.json(
|
|
{ error: 'Player 2 not found or does not belong to your organization' },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
// Check player 2 is not already registered
|
|
const existingInscription2 = await db.tournamentInscription.findFirst({
|
|
where: {
|
|
tournamentId: id,
|
|
OR: [
|
|
{ clientId: player2Id },
|
|
{ partnerId: player2Id },
|
|
],
|
|
},
|
|
});
|
|
|
|
if (existingInscription2) {
|
|
return NextResponse.json(
|
|
{ error: 'Player 2 is already registered for this tournament' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// Create inscription
|
|
const inscription = await db.tournamentInscription.create({
|
|
data: {
|
|
tournamentId: id,
|
|
clientId: player1Id,
|
|
partnerId: player2Id || null,
|
|
teamName: teamName || null,
|
|
seedNumber: tournament._count.inscriptions + 1,
|
|
isPaid: false,
|
|
paidAmount: 0,
|
|
},
|
|
include: {
|
|
client: {
|
|
select: {
|
|
id: true,
|
|
firstName: true,
|
|
lastName: true,
|
|
email: true,
|
|
phone: true,
|
|
level: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
// Get partner info if exists
|
|
let partner = null;
|
|
if (player2Id) {
|
|
partner = await db.client.findUnique({
|
|
where: { id: player2Id },
|
|
select: {
|
|
id: true,
|
|
firstName: true,
|
|
lastName: true,
|
|
email: true,
|
|
phone: true,
|
|
level: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
return NextResponse.json(
|
|
{
|
|
...inscription,
|
|
partner,
|
|
},
|
|
{ status: 201 }
|
|
);
|
|
} catch (error) {
|
|
console.error('Error creating inscription:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to create inscription' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|