from fastapi import FastAPI, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from database.session import get_db 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 app = FastAPI(title="Ticketing System API") app.include_router(auth_router) app.include_router(webhooks_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)): # 1. Проверяем статус в БД (грязное чтение, чтобы отсеять уже выкупленные билеты) query = select(Ticket).where(Ticket.seat_id == seat_id) result = await db.execute(query) ticket = result.scalar_one_or_none() if ticket and ticket.status != TicketStatus.AVAILABLE: raise HTTPException(status_code=409, detail="Seat is already booked or locked in DB") # 2. Пытаемся захватить распределенную блокировку в Redis (15 минут по ТЗ) locked = await acquire_seat_lock(seat_id=seat_id, user_id=user_id) if not locked: raise HTTPException(status_code=409, detail="Seat is currently locked by another user") # 3. Лок наш. Пишем статус в PostgreSQL try: if not ticket: ticket = Ticket(seat_id=seat_id, user_id=user_id, status=TicketStatus.LOCKED) db.add(ticket) else: ticket.status = TicketStatus.LOCKED ticket.user_id = user_id await db.commit() return {"message": "Seat locked successfully", "seat_id": seat_id, "status": "LOCKED"} except Exception as e: # Критически важно: если БД отвалилась, снимаем лок в Redis, иначе место зависнет на 15 минут await release_seat_lock(seat_id) await db.rollback() raise HTTPException(status_code=500, detail="Database transaction failed")