## Skip with Exile (новый расходник) - Новая модель ExiledGame для хранения изгнанных игр - Расходник skip_exile: пропуск без штрафа + игра исключается из пула навсегда - Фильтрация изгнанных игр при выдаче заданий - UI кнопка в PlayPage для использования skip_exile ## Модерация марафонов (для организаторов) - Эндпоинты: skip-assignment, exiled-games, restore-exiled-game - UI в LeaderboardPage: кнопка скипа у каждого участника - Выбор типа скипа (обычный/с изгнанием) + причина - Telegram уведомления о модерации ## Админская выдача предметов - Эндпоинты: admin grant/remove items, get user inventory - Новая страница AdminGrantItemPage (как магазин) - Telegram уведомление при получении подарка ## Исправления миграций - Миграции 029/030 теперь идемпотентны (проверка существования таблиц) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
38 lines
1.6 KiB
Python
38 lines
1.6 KiB
Python
from datetime import datetime
|
|
from sqlalchemy import DateTime, ForeignKey, String, Boolean, Integer, UniqueConstraint
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from app.core.database import Base
|
|
|
|
|
|
class ExiledGame(Base):
|
|
"""Изгнанные игры участника - не будут выпадать при спине"""
|
|
__tablename__ = "exiled_games"
|
|
__table_args__ = (
|
|
UniqueConstraint("participant_id", "game_id", name="unique_participant_game_exile"),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
participant_id: Mapped[int] = mapped_column(
|
|
Integer, ForeignKey("participants.id", ondelete="CASCADE"), index=True
|
|
)
|
|
game_id: Mapped[int] = mapped_column(
|
|
Integer, ForeignKey("games.id", ondelete="CASCADE")
|
|
)
|
|
assignment_id: Mapped[int | None] = mapped_column(
|
|
Integer, ForeignKey("assignments.id", ondelete="SET NULL"), nullable=True
|
|
)
|
|
exiled_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
exiled_by: Mapped[str] = mapped_column(String(20)) # "user" | "organizer" | "admin"
|
|
reason: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
|
|
# Soft-delete для истории
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
|
|
unexiled_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
unexiled_by: Mapped[str | None] = mapped_column(String(20), nullable=True)
|
|
|
|
# Relationships
|
|
participant: Mapped["Participant"] = relationship("Participant")
|
|
game: Mapped["Game"] = relationship("Game")
|
|
assignment: Mapped["Assignment"] = relationship("Assignment")
|