Files
game-marathon/backend/app/models/participant.py

63 lines
2.5 KiB
Python
Raw Normal View History

2025-12-14 02:38:35 +07:00
from datetime import datetime
2025-12-14 20:21:56 +07:00
from enum import Enum
2026-01-05 07:15:50 +07:00
from sqlalchemy import DateTime, ForeignKey, Integer, String, UniqueConstraint, Boolean, Float
2025-12-14 02:38:35 +07:00
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
2025-12-14 20:21:56 +07:00
class ParticipantRole(str, Enum):
PARTICIPANT = "participant"
ORGANIZER = "organizer"
2025-12-14 02:38:35 +07:00
class Participant(Base):
__tablename__ = "participants"
__table_args__ = (
UniqueConstraint("user_id", "marathon_id", name="unique_user_marathon"),
)
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
marathon_id: Mapped[int] = mapped_column(ForeignKey("marathons.id", ondelete="CASCADE"), index=True)
2025-12-14 20:21:56 +07:00
role: Mapped[str] = mapped_column(String(20), default=ParticipantRole.PARTICIPANT.value)
2025-12-14 02:38:35 +07:00
total_points: Mapped[int] = mapped_column(Integer, default=0)
current_streak: Mapped[int] = mapped_column(Integer, default=0)
drop_count: Mapped[int] = mapped_column(Integer, default=0) # For progressive penalty
joined_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
2026-01-05 07:15:50 +07:00
# Shop: coins earned in this marathon
coins_earned: Mapped[int] = mapped_column(Integer, default=0)
# Shop: consumables state
skips_used: Mapped[int] = mapped_column(Integer, default=0)
active_boost_multiplier: Mapped[float | None] = mapped_column(Float, nullable=True)
active_boost_expires_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
has_shield: Mapped[bool] = mapped_column(Boolean, default=False)
2025-12-14 02:38:35 +07:00
# Relationships
user: Mapped["User"] = relationship("User", back_populates="participations")
marathon: Mapped["Marathon"] = relationship("Marathon", back_populates="participants")
assignments: Mapped[list["Assignment"]] = relationship(
"Assignment",
back_populates="participant",
cascade="all, delete-orphan"
)
2025-12-14 20:21:56 +07:00
@property
def is_organizer(self) -> bool:
return self.role == ParticipantRole.ORGANIZER.value
2026-01-05 07:15:50 +07:00
@property
def has_active_boost(self) -> bool:
"""Check if participant has an active boost"""
if self.active_boost_multiplier is None or self.active_boost_expires_at is None:
return False
return datetime.utcnow() < self.active_boost_expires_at
def get_boost_multiplier(self) -> float:
"""Get current boost multiplier (1.0 if no active boost)"""
if self.has_active_boost:
return self.active_boost_multiplier or 1.0
return 1.0