phase 3 18 api tournament
This commit is contained in:
@@ -1,15 +1,64 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy import func, select
|
||||
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, Tournament, User
|
||||
from database.models import Seat, Ticket, TicketStatus, Tournament, User
|
||||
from database.session import get_db
|
||||
from schemas.tournament import SeatGenerateRequest, TournamentCreate, TournamentResponse
|
||||
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,
|
||||
|
||||
@@ -34,3 +34,12 @@ class TournamentResponse(BaseModel):
|
||||
event_date: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class SeatResponse(BaseModel):
|
||||
id: int
|
||||
sector: str
|
||||
row: int
|
||||
number: int
|
||||
price: int
|
||||
is_available: bool
|
||||
|
||||
Reference in New Issue
Block a user