phase 3 18 api tournament
This commit is contained in:
@@ -1,15 +1,64 @@
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
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 sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from api.deps import get_current_superuser
|
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 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 = 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)
|
@router.post("", response_model=TournamentResponse, status_code=status.HTTP_201_CREATED)
|
||||||
async def create_tournament(
|
async def create_tournament(
|
||||||
body: TournamentCreate,
|
body: TournamentCreate,
|
||||||
|
|||||||
@@ -34,3 +34,12 @@ class TournamentResponse(BaseModel):
|
|||||||
event_date: datetime
|
event_date: datetime
|
||||||
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
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