58 lines
2.5 KiB
Python
58 lines
2.5 KiB
Python
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")
|
||
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=["*"], # Для локальной песочницы оставляем открытым
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
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, "ticket_id": ticket.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")
|