This commit is contained in:
2025-12-17 19:50:55 +07:00
parent debdd66458
commit 7e7cdbcd76
10 changed files with 225 additions and 77 deletions

View File

@@ -1,5 +1,6 @@
from datetime import timedelta
import secrets
import string
from fastapi import APIRouter, HTTPException, status
from sqlalchemy import select, func
from sqlalchemy.orm import selectinload
@@ -10,7 +11,7 @@ from app.api.deps import (
get_participant,
)
from app.models import (
Marathon, Participant, MarathonStatus, Game, GameStatus,
Marathon, Participant, MarathonStatus, Game, GameStatus, Challenge,
Assignment, AssignmentStatus, Activity, ActivityType, ParticipantRole,
)
from app.schemas import (
@@ -40,7 +41,7 @@ async def get_marathon_by_code(invite_code: str, db: DbSession):
select(Marathon, func.count(Participant.id).label("participants_count"))
.outerjoin(Participant)
.options(selectinload(Marathon.creator))
.where(Marathon.invite_code == invite_code)
.where(func.upper(Marathon.invite_code) == invite_code.upper())
.group_by(Marathon.id)
)
row = result.first()
@@ -62,7 +63,9 @@ async def get_marathon_by_code(invite_code: str, db: DbSession):
def generate_invite_code() -> str:
return secrets.token_urlsafe(8)
"""Generate a clean 8-character uppercase alphanumeric code."""
alphabet = string.ascii_uppercase + string.digits
return ''.join(secrets.choice(alphabet) for _ in range(8))
async def get_marathon_or_404(db, marathon_id: int) -> Marathon:
@@ -272,15 +275,33 @@ async def start_marathon(marathon_id: int, current_user: CurrentUser, db: DbSess
if marathon.status != MarathonStatus.PREPARING.value:
raise HTTPException(status_code=400, detail="Marathon is not in preparing state")
# Check if there are approved games with challenges
games_count = await db.scalar(
select(func.count()).select_from(Game).where(
# Check if there are approved games
games_result = await db.execute(
select(Game).where(
Game.marathon_id == marathon_id,
Game.status == GameStatus.APPROVED.value,
)
)
if games_count == 0:
raise HTTPException(status_code=400, detail="Add and approve at least one game before starting")
approved_games = games_result.scalars().all()
if len(approved_games) == 0:
raise HTTPException(status_code=400, detail="Добавьте и одобрите хотя бы одну игру")
# Check that all approved games have at least one challenge
games_without_challenges = []
for game in approved_games:
challenge_count = await db.scalar(
select(func.count()).select_from(Challenge).where(Challenge.game_id == game.id)
)
if challenge_count == 0:
games_without_challenges.append(game.title)
if games_without_challenges:
games_list = ", ".join(games_without_challenges)
raise HTTPException(
status_code=400,
detail=f"У следующих игр нет челленджей: {games_list}"
)
marathon.status = MarathonStatus.ACTIVE.value
@@ -332,7 +353,7 @@ async def finish_marathon(marathon_id: int, current_user: CurrentUser, db: DbSes
@router.post("/join", response_model=MarathonResponse)
async def join_marathon(data: JoinMarathon, current_user: CurrentUser, db: DbSession):
result = await db.execute(
select(Marathon).where(Marathon.invite_code == data.invite_code)
select(Marathon).where(func.upper(Marathon.invite_code) == data.invite_code.upper())
)
marathon = result.scalar_one_or_none()