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'; import { Decimal } from '@prisma/client/runtime/library'; // Validation schema for creating a tournament const createTournamentSchema = z.object({ siteId: z.string().cuid('Invalid site ID'), name: z.string().min(1, 'Name is required').max(100), description: z.string().max(500).optional(), date: z.string().datetime({ message: 'Invalid date format' }), endDate: z.string().datetime({ message: 'Invalid end date format' }).optional(), type: z.enum(['SINGLE_ELIMINATION', 'DOUBLE_ELIMINATION', 'ROUND_ROBIN', 'LEAGUE']), category: z.string().max(50).optional(), maxTeams: z.number().int().min(2, 'Minimum 2 teams required'), price: z.number().nonnegative('Price must be non-negative'), }); // Map API types to database types function mapTournamentType(type: string): 'AMERICANO' | 'MEXICANO' | 'BRACKET' | 'ROUND_ROBIN' | 'LEAGUE' { const mapping: Record = { 'SINGLE_ELIMINATION': 'BRACKET', 'DOUBLE_ELIMINATION': 'BRACKET', 'ROUND_ROBIN': 'ROUND_ROBIN', 'LEAGUE': 'LEAGUE', }; return mapping[type] || 'BRACKET'; } // GET /api/tournaments - List tournaments with filters export async function GET(request: NextRequest) { try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); } const { searchParams } = new URL(request.url); const siteId = searchParams.get('siteId'); const status = searchParams.get('status'); const startDate = searchParams.get('startDate'); const endDate = searchParams.get('endDate'); // Build the where clause interface TournamentWhereClause { organizationId: string; siteId?: string; status?: 'DRAFT' | 'REGISTRATION_OPEN' | 'REGISTRATION_CLOSED' | 'IN_PROGRESS' | 'COMPLETED' | 'CANCELLED'; startDate?: { gte?: Date; lte?: Date; }; } const whereClause: TournamentWhereClause = { organizationId: session.user.organizationId, }; // Filter by site if (siteId) { whereClause.siteId = siteId; } else if (session.user.siteId) { whereClause.siteId = session.user.siteId; } // Filter by status if (status) { const validStatuses = ['DRAFT', 'REGISTRATION_OPEN', 'REGISTRATION_CLOSED', 'IN_PROGRESS', 'COMPLETED', 'CANCELLED'] as const; const upperStatus = status.toUpperCase(); if (validStatuses.includes(upperStatus as typeof validStatuses[number])) { whereClause.status = upperStatus as typeof validStatuses[number]; } } // Filter by date range if (startDate || endDate) { whereClause.startDate = {}; if (startDate) { const start = new Date(startDate); start.setHours(0, 0, 0, 0); whereClause.startDate.gte = start; } if (endDate) { const end = new Date(endDate); end.setHours(23, 59, 59, 999); whereClause.startDate.lte = end; } } const tournaments = await db.tournament.findMany({ where: whereClause, include: { site: { select: { id: true, name: true, slug: true, }, }, _count: { select: { inscriptions: true, matches: true, }, }, }, orderBy: { startDate: 'desc', }, }); return NextResponse.json(tournaments); } catch (error) { console.error('Error fetching tournaments:', error); return NextResponse.json( { error: 'Failed to fetch tournaments' }, { status: 500 } ); } } // POST /api/tournaments - Create a new tournament export async function POST(request: NextRequest) { try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); } // Check if user has admin role const allowedRoles = ['SUPER_ADMIN', 'ORG_ADMIN', 'SITE_ADMIN']; if (!allowedRoles.includes(session.user.role)) { return NextResponse.json( { error: 'Forbidden: Insufficient permissions' }, { status: 403 } ); } const body = await request.json(); // Validate input with Zod schema const validationResult = createTournamentSchema.safeParse(body); if (!validationResult.success) { return NextResponse.json( { error: 'Invalid tournament data', details: validationResult.error.flatten().fieldErrors, }, { status: 400 } ); } const { siteId, name, description, date, endDate, type, category, maxTeams, price } = validationResult.data; // Verify site belongs to user's organization const site = await db.site.findFirst({ where: { id: siteId, organizationId: session.user.organizationId, }, }); if (!site) { return NextResponse.json( { error: 'Site not found or does not belong to your organization' }, { status: 404 } ); } // If user is SITE_ADMIN, verify they have access to this site if (session.user.role === 'SITE_ADMIN' && session.user.siteId !== siteId) { return NextResponse.json( { error: 'Forbidden: You do not have access to this site' }, { status: 403 } ); } // Create tournament const tournament = await db.tournament.create({ data: { organizationId: session.user.organizationId, siteId, name, description: description || null, type: mapTournamentType(type), status: 'DRAFT', startDate: new Date(date), endDate: endDate ? new Date(endDate) : null, maxPlayers: maxTeams, entryFee: new Decimal(price), settings: { tournamentFormat: type, category: category || null, }, }, include: { site: { select: { id: true, name: true, slug: true, }, }, _count: { select: { inscriptions: true, matches: true, }, }, }, }); return NextResponse.json(tournament, { status: 201 }); } catch (error) { console.error('Error creating tournament:', error); return NextResponse.json( { error: 'Failed to create tournament' }, { status: 500 } ); } }