fix: resolve schedule data staleness in production builds
- Replace window.location.reload() with router.refresh() in ScheduleGenerator, MatchEditor, RecalculateEloButton for proper Next.js cache invalidation - Add revalidatePath() call after schedule generation in POST handler - Add ownerId to tournament creation in cucumber tests for proper permission checks - Assign tournament_admin role via Prisma after user creation in cucumber tests - Fix TypeScript type annotations in hooks.ts (tournament id map) - Update page reload to use networkidle in common-steps.ts - Clear .next/ cache before cucumber tests in justfile - Add .turbo to clean target - Add comprehensive debug logging to schedule API route - Document findings in docs/TROUBLESHOOTING_SCHEDULE_GENERATION.md
This commit is contained in:
@@ -23,6 +23,7 @@ export default async function TournamentSchedulePage({ params }: PageProps) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
console.log(`[Schedule Page] Fetching tournament ${tournamentId}`);
|
||||
const tournament = await prisma.event.findUnique({
|
||||
where: { id: tournamentId },
|
||||
include: {
|
||||
@@ -48,6 +49,10 @@ export default async function TournamentSchedulePage({ params }: PageProps) {
|
||||
},
|
||||
},
|
||||
})
|
||||
console.log(`[Schedule Page] Tournament ${tournamentId} has ${tournament?.rounds?.length || 0} rounds`);
|
||||
if (tournament?.rounds && tournament.rounds.length > 0) {
|
||||
console.log(`[Schedule Page] First round:`, JSON.stringify(tournament.rounds[0]));
|
||||
}
|
||||
|
||||
if (!tournament) {
|
||||
notFound()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { canManageTournament } from "@/lib/permissions";
|
||||
import { generateRoundRobin, validateScheduleInput, generateVariableRoundRobin, expectedRounds } from "@/lib/schedule-generator";
|
||||
@@ -78,11 +79,15 @@ export async function GET(_request: Request, { params }: RouteParams) {
|
||||
* Creates TournamentRound and BracketMatchup records.
|
||||
*/
|
||||
export async function POST(_request: Request, { params }: RouteParams) {
|
||||
console.log(`[Schedule API] POST handler started`);
|
||||
try {
|
||||
const { id } = await params;
|
||||
const tournamentId = parseInt(id);
|
||||
|
||||
console.log(`[Schedule API] POST /api/tournaments/${tournamentId}/schedule`);
|
||||
|
||||
if (isNaN(tournamentId)) {
|
||||
console.log(`[Schedule API] Invalid tournament ID: ${id}`);
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid tournament ID" },
|
||||
{ status: 400 }
|
||||
@@ -90,6 +95,7 @@ export async function POST(_request: Request, { params }: RouteParams) {
|
||||
}
|
||||
|
||||
const permission = await canManageTournament(tournamentId);
|
||||
console.log(`[Schedule API] Permission check: ${permission.allowed}, reason: ${permission.reason}`);
|
||||
if (!permission.allowed) {
|
||||
return NextResponse.json(
|
||||
{ error: permission.reason || "Not authorized to manage this tournament" },
|
||||
@@ -98,6 +104,7 @@ export async function POST(_request: Request, { params }: RouteParams) {
|
||||
}
|
||||
|
||||
// Check tournament exists
|
||||
console.log(`[Schedule API] Looking up tournament ${tournamentId}`);
|
||||
const tournament = await prisma.event.findUnique({
|
||||
where: { id: tournamentId },
|
||||
include: {
|
||||
@@ -111,14 +118,17 @@ export async function POST(_request: Request, { params }: RouteParams) {
|
||||
});
|
||||
|
||||
if (!tournament) {
|
||||
console.log(`[Schedule API] Tournament ${tournamentId} not found`);
|
||||
return NextResponse.json(
|
||||
{ error: "Tournament not found" },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
console.log(`[Schedule API] Found tournament ${tournamentId} with ${tournament.participants.length} participants and ${tournament.rounds.length} existing rounds`);
|
||||
|
||||
// Check if schedule already exists and delete it
|
||||
if (tournament.rounds.length > 0) {
|
||||
console.log(`[Schedule API] Deleting ${tournament.rounds.length} existing rounds`);
|
||||
// Delete existing rounds and matchups before regenerating
|
||||
await prisma.bracketMatchup.deleteMany({
|
||||
where: { eventId: tournamentId },
|
||||
@@ -135,6 +145,8 @@ export async function POST(_request: Request, { params }: RouteParams) {
|
||||
currentElo: p.player.currentElo,
|
||||
}));
|
||||
|
||||
console.log(`[Schedule API] Got ${participants.length} participants`);
|
||||
|
||||
// Check minimum participants
|
||||
if (participants.length < 2) {
|
||||
return NextResponse.json(
|
||||
@@ -147,20 +159,24 @@ export async function POST(_request: Request, { params }: RouteParams) {
|
||||
const teamDurability = tournament.teamDurability || "permanent";
|
||||
const partnerRotation = (tournament.partnerRotation || "none") as 'none' | 'minimize_repeat' | 'maximize_even' | 'elo_based';
|
||||
const allowByes = tournament.allowByes ?? true;
|
||||
console.log(`[Schedule API] Team durability: ${teamDurability}, partner rotation: ${partnerRotation}, allow byes: ${allowByes}`);
|
||||
|
||||
// Determine number of teams from participants
|
||||
const tempResult = generateTeams(participants, partnerRotation, allowByes);
|
||||
const teamCount = tempResult.teams.length;
|
||||
|
||||
console.log(`[Schedule API] Generated ${teamCount} teams from ${participants.length} participants`);
|
||||
|
||||
if (teamCount < 2) {
|
||||
console.log(`[Schedule API] Not enough teams: ${teamCount}`);
|
||||
return NextResponse.json(
|
||||
{ error: "At least 2 teams (4 players) are required to generate a schedule" },
|
||||
{ error: "At least 2 teams are required to generate a schedule" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate number of rounds needed
|
||||
const numRounds = expectedRounds(teamCount);
|
||||
// Calculate expected rounds
|
||||
const expectedRounds = expectedRounds(teamCount);
|
||||
console.log(`[Schedule API] Expected rounds: ${expectedRounds}`);
|
||||
|
||||
if (teamDurability === "permanent") {
|
||||
// ============================================
|
||||
@@ -186,11 +202,14 @@ export async function POST(_request: Request, { params }: RouteParams) {
|
||||
|
||||
// Generate schedule using fixed teams
|
||||
const schedule = generateRoundRobin(teamPairings);
|
||||
console.log(`[Schedule API] Generated ${schedule.length} rounds for ${teamCount} teams (fixed)`);
|
||||
|
||||
// Create rounds and matchups in a transaction
|
||||
console.log(`[Schedule API] About to create ${schedule.length} rounds in transaction`);
|
||||
const created = await prisma.$transaction(
|
||||
schedule.map((round) =>
|
||||
prisma.tournamentRound.create({
|
||||
schedule.map((round) => {
|
||||
console.log(`[Schedule API] Creating round ${round.roundNumber} with ${round.matchups.length} matchups`);
|
||||
return prisma.tournamentRound.create({
|
||||
data: {
|
||||
eventId: tournamentId,
|
||||
roundNumber: round.roundNumber,
|
||||
@@ -218,8 +237,17 @@ export async function POST(_request: Request, { params }: RouteParams) {
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
);
|
||||
|
||||
console.log(`[Schedule API] Transaction complete. Created ${created.length} rounds`);
|
||||
console.log(`[Schedule API] Verifying in database:`);
|
||||
for (const round of created) {
|
||||
console.log(`[Schedule API] Round ${round.roundNumber}: id=${round.id}, eventId=${round.eventId}`);
|
||||
}
|
||||
|
||||
revalidatePath(`/admin/tournaments/${tournamentId}/schedule`);
|
||||
console.log(`[Schedule API] revalidatePath called for /admin/tournaments/${tournamentId}/schedule`);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
@@ -257,7 +285,7 @@ export async function POST(_request: Request, { params }: RouteParams) {
|
||||
const schedule = generateVariableRoundRobin(
|
||||
participants,
|
||||
teamCount,
|
||||
numRounds,
|
||||
expectedRounds,
|
||||
generateTeamWithRotation
|
||||
);
|
||||
|
||||
@@ -295,6 +323,8 @@ export async function POST(_request: Request, { params }: RouteParams) {
|
||||
)
|
||||
);
|
||||
|
||||
revalidatePath(`/admin/tournaments/${tournamentId}/schedule`);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
roundsCreated: created.length,
|
||||
|
||||
Reference in New Issue
Block a user