import enum import uuid from datetime import datetime, timezone from sqlalchemy import String, Integer, ForeignKey, DateTime, Enum, Boolean from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship class Base(DeclarativeBase): pass class TicketStatus(str, enum.Enum): AVAILABLE = "AVAILABLE" LOCKED = "LOCKED" PAID = "PAID" SCANNED = "SCANNED" REFUNDED = "REFUNDED" class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(primary_key=True) email: Mapped[str] = mapped_column(String, unique=True, index=True) hashed_password: Mapped[str] = mapped_column(String) is_superuser: Mapped[bool] = mapped_column(Boolean, default=False) tickets: Mapped[list["Ticket"]] = relationship(back_populates="user") class Tournament(Base): __tablename__ = "tournaments" id: Mapped[int] = mapped_column(primary_key=True) title: Mapped[str] = mapped_column(String) description: Mapped[str | None] = mapped_column(String, nullable=True) event_date: Mapped[datetime] = mapped_column(DateTime(timezone=True)) is_active: Mapped[bool] = mapped_column(Boolean, default=True) seats: Mapped[list["Seat"]] = relationship(back_populates="tournament") class Seat(Base): __tablename__ = "seats" id: Mapped[int] = mapped_column(primary_key=True) tournament_id: Mapped[int] = mapped_column(ForeignKey("tournaments.id"), index=True) sector: Mapped[str] = mapped_column(String) row: Mapped[int] = mapped_column(Integer) number: Mapped[int] = mapped_column(Integer) price: Mapped[int] = mapped_column(Integer) tournament: Mapped["Tournament"] = relationship(back_populates="seats") ticket: Mapped["Ticket"] = relationship(back_populates="seat", uselist=False) class Ticket(Base): __tablename__ = "tickets" id: Mapped[int] = mapped_column(primary_key=True) seat_id: Mapped[int] = mapped_column(ForeignKey("seats.id"), unique=True, index=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=True, index=True) status: Mapped[TicketStatus] = mapped_column( Enum(TicketStatus, name="ticket_status_enum", create_type=False), default=TicketStatus.AVAILABLE, index=True ) idempotency_key: Mapped[str] = mapped_column(String, unique=True, nullable=True) pdf_url: Mapped[str | None] = mapped_column(String, nullable=True) # nullable=True — безопасно для существующих строк; новые билеты получают UUID автоматически secret_token: Mapped[str | None] = mapped_column( String, unique=True, index=True, nullable=True, default=lambda: str(uuid.uuid4()), ) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc) ) seat: Mapped["Seat"] = relationship(back_populates="ticket") user: Mapped["User"] = relationship(back_populates="tickets")