Add challenges preview + makefile

This commit is contained in:
2025-12-14 03:23:50 +07:00
parent 5343a8f2c3
commit bb9e9a6e1d
7 changed files with 590 additions and 37 deletions

View File

@@ -10,6 +10,9 @@ from app.schemas import (
ChallengeResponse,
MessageResponse,
GameShort,
ChallengePreview,
ChallengesPreviewResponse,
ChallengesSaveRequest,
)
from app.services.gpt import GPTService
@@ -136,9 +139,9 @@ async def create_challenge(
)
@router.post("/marathons/{marathon_id}/generate-challenges", response_model=MessageResponse)
async def generate_challenges(marathon_id: int, current_user: CurrentUser, db: DbSession):
"""Generate challenges for all games in marathon using GPT"""
@router.post("/marathons/{marathon_id}/preview-challenges", response_model=ChallengesPreviewResponse)
async def preview_challenges(marathon_id: int, current_user: CurrentUser, db: DbSession):
"""Generate challenges preview for all games in marathon using GPT (without saving)"""
# Check marathon
result = await db.execute(select(Marathon).where(Marathon.id == marathon_id))
marathon = result.scalar_one_or_none()
@@ -159,7 +162,7 @@ async def generate_challenges(marathon_id: int, current_user: CurrentUser, db: D
if not games:
raise HTTPException(status_code=400, detail="No games in marathon")
generated_count = 0
preview_challenges = []
for game in games:
# Check if game already has challenges
existing = await db.scalar(
@@ -172,8 +175,9 @@ async def generate_challenges(marathon_id: int, current_user: CurrentUser, db: D
challenges_data = await gpt_service.generate_challenges(game.title, game.genre)
for ch_data in challenges_data:
challenge = Challenge(
preview_challenges.append(ChallengePreview(
game_id=game.id,
game_title=game.title,
title=ch_data.title,
description=ch_data.description,
type=ch_data.type,
@@ -182,18 +186,78 @@ async def generate_challenges(marathon_id: int, current_user: CurrentUser, db: D
estimated_time=ch_data.estimated_time,
proof_type=ch_data.proof_type,
proof_hint=ch_data.proof_hint,
is_generated=True,
)
db.add(challenge)
generated_count += 1
))
except Exception as e:
# Log error but continue with other games
print(f"Error generating challenges for {game.title}: {e}")
return ChallengesPreviewResponse(challenges=preview_challenges)
@router.post("/marathons/{marathon_id}/save-challenges", response_model=MessageResponse)
async def save_challenges(
marathon_id: int,
data: ChallengesSaveRequest,
current_user: CurrentUser,
db: DbSession,
):
"""Save previewed challenges to database"""
# Check marathon
result = await db.execute(select(Marathon).where(Marathon.id == marathon_id))
marathon = result.scalar_one_or_none()
if not marathon:
raise HTTPException(status_code=404, detail="Marathon not found")
if marathon.status != MarathonStatus.PREPARING.value:
raise HTTPException(status_code=400, detail="Cannot add challenges to active or finished marathon")
await check_participant(db, current_user.id, marathon_id)
# Verify all games belong to this marathon
result = await db.execute(
select(Game.id).where(Game.marathon_id == marathon_id)
)
valid_game_ids = set(row[0] for row in result.fetchall())
saved_count = 0
for ch_data in data.challenges:
if ch_data.game_id not in valid_game_ids:
continue # Skip challenges for invalid games
# Validate type
ch_type = ch_data.type
if ch_type not in ["completion", "no_death", "speedrun", "collection", "achievement", "challenge_run"]:
ch_type = "completion"
# Validate difficulty
difficulty = ch_data.difficulty
if difficulty not in ["easy", "medium", "hard"]:
difficulty = "medium"
# Validate proof_type
proof_type = ch_data.proof_type
if proof_type not in ["screenshot", "video", "steam"]:
proof_type = "screenshot"
challenge = Challenge(
game_id=ch_data.game_id,
title=ch_data.title[:100],
description=ch_data.description,
type=ch_type,
difficulty=difficulty,
points=max(1, min(500, ch_data.points)),
estimated_time=ch_data.estimated_time,
proof_type=proof_type,
proof_hint=ch_data.proof_hint,
is_generated=True,
)
db.add(challenge)
saved_count += 1
await db.commit()
return MessageResponse(message=f"Generated {generated_count} challenges")
return MessageResponse(message=f"Сохранено {saved_count} заданий")
@router.patch("/challenges/{challenge_id}", response_model=ChallengeResponse)

View File

@@ -28,6 +28,10 @@ from app.schemas.challenge import (
ChallengeUpdate,
ChallengeResponse,
ChallengeGenerated,
ChallengePreview,
ChallengesPreviewResponse,
ChallengeSaveItem,
ChallengesSaveRequest,
)
from app.schemas.assignment import (
CompleteAssignment,
@@ -74,6 +78,10 @@ __all__ = [
"ChallengeUpdate",
"ChallengeResponse",
"ChallengeGenerated",
"ChallengePreview",
"ChallengesPreviewResponse",
"ChallengeSaveItem",
"ChallengesSaveRequest",
# Assignment
"CompleteAssignment",
"AssignmentResponse",

View File

@@ -51,3 +51,40 @@ class ChallengeGenerated(BaseModel):
estimated_time: int | None = None
proof_type: str
proof_hint: str | None = None
class ChallengePreview(BaseModel):
"""Schema for challenge preview (with game info)"""
game_id: int
game_title: str
title: str
description: str
type: str
difficulty: str
points: int
estimated_time: int | None = None
proof_type: str
proof_hint: str | None = None
class ChallengesPreviewResponse(BaseModel):
"""Response with generated challenges for preview"""
challenges: list[ChallengePreview]
class ChallengeSaveItem(BaseModel):
"""Single challenge to save"""
game_id: int
title: str
description: str
type: str
difficulty: str
points: int
estimated_time: int | None = None
proof_type: str
proof_hint: str | None = None
class ChallengesSaveRequest(BaseModel):
"""Request to save previewed challenges"""
challenges: list[ChallengeSaveItem]