Добавлена поддержка обмена играми с типом прохождения (playthrough)
- Обновлены схемы SwapCandidate и SwapRequestChallengeInfo для поддержки прохождений - get_swap_candidates теперь возвращает и челленджи, и прохождения - accept_swap_request теперь корректно меняет challenge_id, game_id, is_playthrough и bonus_assignments - Обновлён UI для отображения прохождений в списке кандидатов и запросах обмена Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ from app.models import (
|
||||
Event, EventType, Activity, ActivityType, Assignment, AssignmentStatus, Challenge, Game,
|
||||
SwapRequest as SwapRequestModel, SwapRequestStatus, User,
|
||||
)
|
||||
from app.models.bonus_assignment import BonusAssignment
|
||||
from fastapi import UploadFile, File, Form
|
||||
|
||||
from app.schemas import (
|
||||
@@ -275,6 +276,26 @@ async def stop_event(
|
||||
return MessageResponse(message="Event stopped")
|
||||
|
||||
|
||||
def build_assignment_info(assignment: Assignment) -> SwapRequestChallengeInfo:
|
||||
"""Build SwapRequestChallengeInfo from assignment (challenge or playthrough)"""
|
||||
if assignment.is_playthrough:
|
||||
return SwapRequestChallengeInfo(
|
||||
is_playthrough=True,
|
||||
playthrough_description=assignment.game.playthrough_description,
|
||||
playthrough_points=assignment.game.playthrough_points,
|
||||
game_title=assignment.game.title,
|
||||
)
|
||||
else:
|
||||
return SwapRequestChallengeInfo(
|
||||
is_playthrough=False,
|
||||
title=assignment.challenge.title,
|
||||
description=assignment.challenge.description,
|
||||
points=assignment.challenge.points,
|
||||
difficulty=assignment.challenge.difficulty,
|
||||
game_title=assignment.challenge.game.title,
|
||||
)
|
||||
|
||||
|
||||
def build_swap_request_response(
|
||||
swap_req: SwapRequestModel,
|
||||
) -> SwapRequestResponse:
|
||||
@@ -298,20 +319,8 @@ def build_swap_request_response(
|
||||
role=swap_req.to_participant.user.role,
|
||||
created_at=swap_req.to_participant.user.created_at,
|
||||
),
|
||||
from_challenge=SwapRequestChallengeInfo(
|
||||
title=swap_req.from_assignment.challenge.title,
|
||||
description=swap_req.from_assignment.challenge.description,
|
||||
points=swap_req.from_assignment.challenge.points,
|
||||
difficulty=swap_req.from_assignment.challenge.difficulty,
|
||||
game_title=swap_req.from_assignment.challenge.game.title,
|
||||
),
|
||||
to_challenge=SwapRequestChallengeInfo(
|
||||
title=swap_req.to_assignment.challenge.title,
|
||||
description=swap_req.to_assignment.challenge.description,
|
||||
points=swap_req.to_assignment.challenge.points,
|
||||
difficulty=swap_req.to_assignment.challenge.difficulty,
|
||||
game_title=swap_req.to_assignment.challenge.game.title,
|
||||
),
|
||||
from_challenge=build_assignment_info(swap_req.from_assignment),
|
||||
to_challenge=build_assignment_info(swap_req.to_assignment),
|
||||
created_at=swap_req.created_at,
|
||||
responded_at=swap_req.responded_at,
|
||||
)
|
||||
@@ -349,11 +358,12 @@ async def create_swap_request(
|
||||
if target.id == participant.id:
|
||||
raise HTTPException(status_code=400, detail="Cannot swap with yourself")
|
||||
|
||||
# Get both active assignments
|
||||
# Get both active assignments (with challenge.game or game for playthrough)
|
||||
result = await db.execute(
|
||||
select(Assignment)
|
||||
.options(
|
||||
selectinload(Assignment.challenge).selectinload(Challenge.game)
|
||||
selectinload(Assignment.challenge).selectinload(Challenge.game),
|
||||
selectinload(Assignment.game), # For playthrough
|
||||
)
|
||||
.where(
|
||||
Assignment.participant_id == participant.id,
|
||||
@@ -365,7 +375,8 @@ async def create_swap_request(
|
||||
result = await db.execute(
|
||||
select(Assignment)
|
||||
.options(
|
||||
selectinload(Assignment.challenge).selectinload(Challenge.game)
|
||||
selectinload(Assignment.challenge).selectinload(Challenge.game),
|
||||
selectinload(Assignment.game), # For playthrough
|
||||
)
|
||||
.where(
|
||||
Assignment.participant_id == target.id,
|
||||
@@ -417,7 +428,7 @@ async def create_swap_request(
|
||||
await db.commit()
|
||||
await db.refresh(swap_request)
|
||||
|
||||
# Load relationships for response
|
||||
# Load relationships for response (including game for playthrough)
|
||||
result = await db.execute(
|
||||
select(SwapRequestModel)
|
||||
.options(
|
||||
@@ -426,9 +437,13 @@ async def create_swap_request(
|
||||
selectinload(SwapRequestModel.from_assignment)
|
||||
.selectinload(Assignment.challenge)
|
||||
.selectinload(Challenge.game),
|
||||
selectinload(SwapRequestModel.from_assignment)
|
||||
.selectinload(Assignment.game),
|
||||
selectinload(SwapRequestModel.to_assignment)
|
||||
.selectinload(Assignment.challenge)
|
||||
.selectinload(Challenge.game),
|
||||
selectinload(SwapRequestModel.to_assignment)
|
||||
.selectinload(Assignment.game),
|
||||
)
|
||||
.where(SwapRequestModel.id == swap_request.id)
|
||||
)
|
||||
@@ -461,9 +476,13 @@ async def get_my_swap_requests(
|
||||
selectinload(SwapRequestModel.from_assignment)
|
||||
.selectinload(Assignment.challenge)
|
||||
.selectinload(Challenge.game),
|
||||
selectinload(SwapRequestModel.from_assignment)
|
||||
.selectinload(Assignment.game),
|
||||
selectinload(SwapRequestModel.to_assignment)
|
||||
.selectinload(Assignment.challenge)
|
||||
.selectinload(Challenge.game),
|
||||
selectinload(SwapRequestModel.to_assignment)
|
||||
.selectinload(Assignment.game),
|
||||
)
|
||||
.where(
|
||||
SwapRequestModel.event_id == event.id,
|
||||
@@ -553,10 +572,39 @@ async def accept_swap_request(
|
||||
await db.commit()
|
||||
raise HTTPException(status_code=400, detail="One or both assignments are no longer active")
|
||||
|
||||
# Perform the swap
|
||||
# Perform the swap (swap challenge_id, game_id, and is_playthrough)
|
||||
from_challenge_id = from_assignment.challenge_id
|
||||
from_game_id = from_assignment.game_id
|
||||
from_is_playthrough = from_assignment.is_playthrough
|
||||
|
||||
from_assignment.challenge_id = to_assignment.challenge_id
|
||||
from_assignment.game_id = to_assignment.game_id
|
||||
from_assignment.is_playthrough = to_assignment.is_playthrough
|
||||
|
||||
to_assignment.challenge_id = from_challenge_id
|
||||
to_assignment.game_id = from_game_id
|
||||
to_assignment.is_playthrough = from_is_playthrough
|
||||
|
||||
# Swap bonus assignments between the two assignments
|
||||
from sqlalchemy import update as sql_update
|
||||
|
||||
# Get bonus assignments for both
|
||||
from_bonus_result = await db.execute(
|
||||
select(BonusAssignment).where(BonusAssignment.main_assignment_id == from_assignment.id)
|
||||
)
|
||||
from_bonus_assignments = from_bonus_result.scalars().all()
|
||||
|
||||
to_bonus_result = await db.execute(
|
||||
select(BonusAssignment).where(BonusAssignment.main_assignment_id == to_assignment.id)
|
||||
)
|
||||
to_bonus_assignments = to_bonus_result.scalars().all()
|
||||
|
||||
# Move bonus assignments: from -> to, to -> from
|
||||
for bonus in from_bonus_assignments:
|
||||
bonus.main_assignment_id = to_assignment.id
|
||||
|
||||
for bonus in to_bonus_assignments:
|
||||
bonus.main_assignment_id = from_assignment.id
|
||||
|
||||
# Update request status
|
||||
swap_request.status = SwapRequestStatus.ACCEPTED.value
|
||||
@@ -865,8 +913,10 @@ async def get_swap_candidates(
|
||||
if not event or event.type != EventType.SWAP.value:
|
||||
raise HTTPException(status_code=400, detail="No active swap event")
|
||||
|
||||
# Get all participants except current user with active assignments
|
||||
from app.models import Game
|
||||
candidates = []
|
||||
|
||||
# Get challenge-based assignments
|
||||
result = await db.execute(
|
||||
select(Participant, Assignment, Challenge, Game)
|
||||
.join(Assignment, Assignment.participant_id == Participant.id)
|
||||
@@ -877,12 +927,11 @@ async def get_swap_candidates(
|
||||
Participant.marathon_id == marathon_id,
|
||||
Participant.id != participant.id,
|
||||
Assignment.status == AssignmentStatus.ACTIVE.value,
|
||||
Assignment.is_playthrough == False,
|
||||
)
|
||||
)
|
||||
rows = result.all()
|
||||
|
||||
return [
|
||||
SwapCandidate(
|
||||
for p, assignment, challenge, game in result.all():
|
||||
candidates.append(SwapCandidate(
|
||||
participant_id=p.id,
|
||||
user=UserPublic(
|
||||
id=p.user.id,
|
||||
@@ -892,14 +941,45 @@ async def get_swap_candidates(
|
||||
role=p.user.role,
|
||||
created_at=p.user.created_at,
|
||||
),
|
||||
is_playthrough=False,
|
||||
challenge_title=challenge.title,
|
||||
challenge_description=challenge.description,
|
||||
challenge_points=challenge.points,
|
||||
challenge_difficulty=challenge.difficulty,
|
||||
game_title=game.title,
|
||||
))
|
||||
|
||||
# Get playthrough-based assignments
|
||||
result = await db.execute(
|
||||
select(Participant, Assignment, Game)
|
||||
.join(Assignment, Assignment.participant_id == Participant.id)
|
||||
.join(Game, Assignment.game_id == Game.id)
|
||||
.options(selectinload(Participant.user))
|
||||
.where(
|
||||
Participant.marathon_id == marathon_id,
|
||||
Participant.id != participant.id,
|
||||
Assignment.status == AssignmentStatus.ACTIVE.value,
|
||||
Assignment.is_playthrough == True,
|
||||
)
|
||||
for p, assignment, challenge, game in rows
|
||||
]
|
||||
)
|
||||
for p, assignment, game in result.all():
|
||||
candidates.append(SwapCandidate(
|
||||
participant_id=p.id,
|
||||
user=UserPublic(
|
||||
id=p.user.id,
|
||||
login=p.user.login,
|
||||
nickname=p.user.nickname,
|
||||
avatar_url=None,
|
||||
role=p.user.role,
|
||||
created_at=p.user.created_at,
|
||||
),
|
||||
is_playthrough=True,
|
||||
playthrough_description=game.playthrough_description,
|
||||
playthrough_points=game.playthrough_points,
|
||||
game_title=game.title,
|
||||
))
|
||||
|
||||
return candidates
|
||||
|
||||
|
||||
@router.get("/marathons/{marathon_id}/common-enemy-leaderboard", response_model=list[CommonEnemyLeaderboard])
|
||||
|
||||
@@ -128,10 +128,16 @@ class SwapCandidate(BaseModel):
|
||||
"""Participant available for assignment swap"""
|
||||
participant_id: int
|
||||
user: UserPublic
|
||||
challenge_title: str
|
||||
challenge_description: str
|
||||
challenge_points: int
|
||||
challenge_difficulty: str
|
||||
is_playthrough: bool = False
|
||||
# Challenge fields (used when is_playthrough=False)
|
||||
challenge_title: str | None = None
|
||||
challenge_description: str | None = None
|
||||
challenge_points: int | None = None
|
||||
challenge_difficulty: str | None = None
|
||||
# Playthrough fields (used when is_playthrough=True)
|
||||
playthrough_description: str | None = None
|
||||
playthrough_points: int | None = None
|
||||
# Common field
|
||||
game_title: str
|
||||
|
||||
|
||||
@@ -145,11 +151,17 @@ class SwapRequestCreate(BaseModel):
|
||||
|
||||
|
||||
class SwapRequestChallengeInfo(BaseModel):
|
||||
"""Challenge info for swap request display"""
|
||||
title: str
|
||||
description: str
|
||||
points: int
|
||||
difficulty: str
|
||||
"""Challenge or playthrough info for swap request display"""
|
||||
is_playthrough: bool = False
|
||||
# Challenge fields (used when is_playthrough=False)
|
||||
title: str | None = None
|
||||
description: str | None = None
|
||||
points: int | None = None
|
||||
difficulty: str | None = None
|
||||
# Playthrough fields (used when is_playthrough=True)
|
||||
playthrough_description: str | None = None
|
||||
playthrough_points: int | None = None
|
||||
# Common field
|
||||
game_title: str
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user