126 lines
4.4 KiB
Python
126 lines
4.4 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, status
|
||
from sqlalchemy import and_, case, func, select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from api.deps import get_current_superuser
|
||
from database.models import Seat, Ticket, TicketStatus, Tournament, User
|
||
from database.session import get_db
|
||
from schemas.tournament import SeatGenerateRequest, SeatResponse, TournamentCreate, TournamentResponse
|
||
|
||
router = APIRouter(prefix="/api/tournaments", tags=["tournaments"])
|
||
|
||
|
||
@router.get("/{tournament_id}/seats", response_model=list[SeatResponse])
|
||
async def get_tournament_seats(
|
||
tournament_id: int,
|
||
db: AsyncSession = Depends(get_db),
|
||
) -> list[SeatResponse]:
|
||
"""
|
||
Публичный эндпоинт. Возвращает все места турнира с флагом is_available.
|
||
|
||
Место недоступно (is_available=False), если связанный Ticket
|
||
находится в статусе LOCKED или PAID.
|
||
Используется LEFT OUTER JOIN + CASE для вычисления флага одним запросом.
|
||
"""
|
||
# 1. Вычисляем is_available через CASE на стороне БД
|
||
is_available_expr = case(
|
||
(
|
||
and_(
|
||
Ticket.id.is_not(None),
|
||
Ticket.status.in_([TicketStatus.LOCKED, TicketStatus.PAID]),
|
||
),
|
||
False,
|
||
),
|
||
else_=True,
|
||
).label("is_available")
|
||
|
||
# 2. LEFT JOIN: берём все места турнира, присоединяем Ticket (если есть)
|
||
stmt = (
|
||
select(Seat, is_available_expr)
|
||
.outerjoin(Ticket, Ticket.seat_id == Seat.id)
|
||
.where(Seat.tournament_id == tournament_id)
|
||
.order_by(Seat.sector, Seat.row, Seat.number)
|
||
)
|
||
|
||
# 3. Выполняем запрос и формируем ответ
|
||
result = await db.execute(stmt)
|
||
rows = result.all()
|
||
|
||
return [
|
||
SeatResponse(
|
||
id=seat.id,
|
||
sector=seat.sector,
|
||
row=seat.row,
|
||
number=seat.number,
|
||
price=seat.price,
|
||
is_available=bool(is_available),
|
||
)
|
||
for seat, is_available in rows
|
||
]
|
||
|
||
|
||
@router.post("", response_model=TournamentResponse, status_code=status.HTTP_201_CREATED)
|
||
async def create_tournament(
|
||
body: TournamentCreate,
|
||
current_user: User = Depends(get_current_superuser),
|
||
db: AsyncSession = Depends(get_db),
|
||
) -> Tournament:
|
||
tournament = Tournament(
|
||
title=body.title,
|
||
description=body.description,
|
||
event_date=body.event_date,
|
||
)
|
||
db.add(tournament)
|
||
await db.commit()
|
||
await db.refresh(tournament)
|
||
return tournament
|
||
|
||
|
||
@router.post(
|
||
"/{tournament_id}/generate-seats",
|
||
status_code=status.HTTP_200_OK,
|
||
)
|
||
async def generate_seats(
|
||
tournament_id: int,
|
||
request: SeatGenerateRequest,
|
||
current_user: User = Depends(get_current_superuser),
|
||
db: AsyncSession = Depends(get_db),
|
||
) -> dict[str, str]:
|
||
# 1. Проверяем существование турнира
|
||
result = await db.execute(select(Tournament).where(Tournament.id == tournament_id))
|
||
tournament: Tournament | None = result.scalar_one_or_none()
|
||
if tournament is None:
|
||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tournament not found")
|
||
|
||
# 2. Проверяем, что места ещё не генерировались (защита от дублей)
|
||
count_result = await db.execute(
|
||
select(func.count()).where(Seat.tournament_id == tournament_id)
|
||
)
|
||
existing_count: int = count_result.scalar_one()
|
||
if existing_count > 0:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail="Места уже сгенерированы",
|
||
)
|
||
|
||
# 3. Генерируем места для каждого сектора
|
||
seats: list[Seat] = []
|
||
for sector_cfg in request.sectors:
|
||
for row in range(1, sector_cfg.rows + 1):
|
||
for number in range(1, sector_cfg.seats_per_row + 1):
|
||
seats.append(
|
||
Seat(
|
||
tournament_id=tournament_id,
|
||
sector=sector_cfg.sector_name,
|
||
row=row,
|
||
number=number,
|
||
price=sector_cfg.price,
|
||
)
|
||
)
|
||
|
||
# 4. Массовая вставка
|
||
db.add_all(seats)
|
||
await db.commit()
|
||
|
||
return {"message": f"Generated {len(seats)} seats successfully"}
|