diff --git a/backend/api/routers/tickets.py b/backend/api/routers/tickets.py new file mode 100644 index 0000000..99f56b0 --- /dev/null +++ b/backend/api/routers/tickets.py @@ -0,0 +1,29 @@ +from fastapi import APIRouter, Depends +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import selectinload + +from api.deps import get_current_user +from database.models import Seat, Ticket, TicketStatus, User +from database.session import get_db +from schemas.ticket import TicketResponse + +router = APIRouter(prefix="/api/tickets", tags=["tickets"]) + + +@router.get("/me", response_model=list[TicketResponse]) +async def get_my_tickets( + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +) -> list[Ticket]: + """Возвращает все оплаченные билеты текущего пользователя.""" + result = await db.execute( + select(Ticket) + .where(Ticket.user_id == current_user.id, Ticket.status == TicketStatus.PAID) + .options( + # Ticket → Seat → Tournament (один запрос на каждый уровень, без N+1) + selectinload(Ticket.seat).selectinload(Seat.tournament) + ) + .order_by(Ticket.created_at.desc()) + ) + return list(result.scalars().all()) diff --git a/backend/main.py b/backend/main.py index b56e2b2..ea7def1 100644 --- a/backend/main.py +++ b/backend/main.py @@ -7,6 +7,7 @@ from database.models import Ticket, TicketStatus from core.redis import acquire_seat_lock, release_seat_lock from api.routers.auth import router as auth_router from api.routers.webhooks import router as webhooks_router +from api.routers.tickets import router as tickets_router app = FastAPI(title="Ticketing System API") @@ -22,6 +23,7 @@ app.add_middleware( app.include_router(auth_router) app.include_router(webhooks_router) +app.include_router(tickets_router) @app.post("/api/seats/{seat_id}/lock", status_code=status.HTTP_200_OK) async def lock_seat(seat_id: int, user_id: int, db: AsyncSession = Depends(get_db)): diff --git a/backend/requirements.txt b/backend/requirements.txt index aedb0cd..ba7d555 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -6,6 +6,7 @@ asyncpg psycopg2-binary redis passlib[bcrypt] +bcrypt==3.2.2 PyJWT aio-pika reportlab diff --git a/backend/schemas/ticket.py b/backend/schemas/ticket.py new file mode 100644 index 0000000..5e63d1c --- /dev/null +++ b/backend/schemas/ticket.py @@ -0,0 +1,34 @@ +from datetime import datetime + +from pydantic import BaseModel, ConfigDict + +from database.models import TicketStatus + + +class TournamentInfo(BaseModel): + id: int + title: str + event_date: datetime + + model_config = ConfigDict(from_attributes=True) + + +class SeatInfo(BaseModel): + id: int + sector: str + row: int + number: int + price: int + tournament: TournamentInfo + + model_config = ConfigDict(from_attributes=True) + + +class TicketResponse(BaseModel): + id: int + status: TicketStatus + pdf_url: str | None + created_at: datetime + seat: SeatInfo + + model_config = ConfigDict(from_attributes=True)