Fix common enemy
This commit is contained in:
@@ -7,7 +7,7 @@ from sqlalchemy.orm import selectinload
|
||||
from app.api.deps import DbSession, CurrentUser
|
||||
from app.models import (
|
||||
Marathon, MarathonStatus, Participant, ParticipantRole,
|
||||
Event, EventType, Activity, ActivityType, Assignment, AssignmentStatus, Challenge,
|
||||
Event, EventType, Activity, ActivityType, Assignment, AssignmentStatus, Challenge, Game,
|
||||
SwapRequest as SwapRequestModel, SwapRequestStatus, User,
|
||||
)
|
||||
from fastapi import UploadFile, File, Form
|
||||
@@ -150,6 +150,46 @@ async def start_event(
|
||||
detail="Common enemy event requires challenge_id"
|
||||
)
|
||||
|
||||
# Handle playthrough games (negative challenge_id = -game_id)
|
||||
challenge_id = data.challenge_id
|
||||
game_id = None
|
||||
is_playthrough = False
|
||||
|
||||
if data.type == EventType.COMMON_ENEMY.value and challenge_id and challenge_id < 0:
|
||||
# This is a playthrough game, not a real challenge
|
||||
game_id = -challenge_id # Convert negative to positive game_id
|
||||
challenge_id = None
|
||||
is_playthrough = True
|
||||
|
||||
# Verify game exists and is a playthrough game
|
||||
from app.models.game import GameType
|
||||
result = await db.execute(
|
||||
select(Game).where(
|
||||
Game.id == game_id,
|
||||
Game.marathon_id == marathon_id,
|
||||
Game.game_type == GameType.PLAYTHROUGH.value,
|
||||
)
|
||||
)
|
||||
game = result.scalar_one_or_none()
|
||||
if not game:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Playthrough game not found"
|
||||
)
|
||||
elif data.type == EventType.COMMON_ENEMY.value and challenge_id and challenge_id > 0:
|
||||
# Verify regular challenge exists
|
||||
result = await db.execute(
|
||||
select(Challenge)
|
||||
.options(selectinload(Challenge.game))
|
||||
.where(Challenge.id == challenge_id)
|
||||
)
|
||||
challenge = result.scalar_one_or_none()
|
||||
if not challenge or challenge.game.marathon_id != marathon_id:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Challenge not found in this marathon"
|
||||
)
|
||||
|
||||
try:
|
||||
event = await event_service.start_event(
|
||||
db=db,
|
||||
@@ -157,7 +197,9 @@ async def start_event(
|
||||
event_type=data.type,
|
||||
created_by_id=current_user.id,
|
||||
duration_minutes=data.duration_minutes,
|
||||
challenge_id=data.challenge_id,
|
||||
challenge_id=challenge_id,
|
||||
game_id=game_id,
|
||||
is_playthrough=is_playthrough,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
@@ -919,6 +961,41 @@ async def get_common_enemy_leaderboard(
|
||||
|
||||
def assignment_to_response(assignment: Assignment) -> AssignmentResponse:
|
||||
"""Convert Assignment model to AssignmentResponse"""
|
||||
# Handle playthrough assignments (no challenge, only game)
|
||||
if assignment.is_playthrough and assignment.game:
|
||||
game = assignment.game
|
||||
return AssignmentResponse(
|
||||
id=assignment.id,
|
||||
challenge=ChallengeResponse(
|
||||
id=-game.id, # Negative ID for playthrough
|
||||
title=f"Прохождение: {game.title}",
|
||||
description=game.playthrough_description or "Пройдите игру",
|
||||
type="completion",
|
||||
difficulty="medium",
|
||||
points=game.playthrough_points or 0,
|
||||
estimated_time=None,
|
||||
proof_type=game.playthrough_proof_type or "screenshot",
|
||||
proof_hint=game.playthrough_proof_hint,
|
||||
game=GameShort(
|
||||
id=game.id,
|
||||
title=game.title,
|
||||
cover_url=storage_service.get_url(game.cover_path, "covers"),
|
||||
download_url=game.download_url,
|
||||
game_type=game.game_type,
|
||||
),
|
||||
is_generated=False,
|
||||
created_at=game.created_at,
|
||||
),
|
||||
status=assignment.status,
|
||||
proof_url=storage_service.get_url(assignment.proof_path, "proofs") if assignment.proof_path else assignment.proof_url,
|
||||
proof_comment=assignment.proof_comment,
|
||||
points_earned=assignment.points_earned,
|
||||
streak_at_completion=assignment.streak_at_completion,
|
||||
started_at=assignment.started_at,
|
||||
completed_at=assignment.completed_at,
|
||||
)
|
||||
|
||||
# Regular challenge assignment
|
||||
challenge = assignment.challenge
|
||||
game = challenge.game
|
||||
return AssignmentResponse(
|
||||
@@ -969,7 +1046,8 @@ async def get_event_assignment(
|
||||
result = await db.execute(
|
||||
select(Assignment)
|
||||
.options(
|
||||
selectinload(Assignment.challenge).selectinload(Challenge.game)
|
||||
selectinload(Assignment.challenge).selectinload(Challenge.game),
|
||||
selectinload(Assignment.game), # For playthrough assignments
|
||||
)
|
||||
.where(
|
||||
Assignment.participant_id == participant.id,
|
||||
@@ -1000,10 +1078,19 @@ async def get_event_assignment(
|
||||
is_completed=False,
|
||||
)
|
||||
|
||||
# Determine challenge_id for response (negative for playthrough)
|
||||
challenge_id_response = None
|
||||
if event and event.data:
|
||||
if event.data.get("is_playthrough"):
|
||||
game_id = event.data.get("game_id")
|
||||
challenge_id_response = -game_id if game_id else None
|
||||
else:
|
||||
challenge_id_response = event.data.get("challenge_id")
|
||||
|
||||
return EventAssignmentResponse(
|
||||
assignment=assignment_to_response(assignment) if assignment else None,
|
||||
event_id=event.id if event else None,
|
||||
challenge_id=event.data.get("challenge_id") if event and event.data else None,
|
||||
challenge_id=challenge_id_response,
|
||||
is_completed=is_completed,
|
||||
)
|
||||
|
||||
@@ -1027,6 +1114,7 @@ async def complete_event_assignment(
|
||||
.options(
|
||||
selectinload(Assignment.participant),
|
||||
selectinload(Assignment.challenge).selectinload(Challenge.game),
|
||||
selectinload(Assignment.game), # For playthrough assignments
|
||||
)
|
||||
.where(Assignment.id == assignment_id)
|
||||
)
|
||||
@@ -1080,17 +1168,25 @@ async def complete_event_assignment(
|
||||
|
||||
assignment.proof_comment = comment
|
||||
|
||||
# Get marathon_id
|
||||
marathon_id = assignment.challenge.game.marathon_id
|
||||
# Get marathon_id and base points (handle playthrough vs regular challenge)
|
||||
participant = assignment.participant
|
||||
if assignment.is_playthrough and assignment.game:
|
||||
marathon_id = assignment.game.marathon_id
|
||||
base_points = assignment.game.playthrough_points or 0
|
||||
challenge_title = f"Прохождение: {assignment.game.title}"
|
||||
game_title = assignment.game.title
|
||||
difficulty = "medium"
|
||||
else:
|
||||
challenge = assignment.challenge
|
||||
marathon_id = challenge.game.marathon_id
|
||||
base_points = challenge.points
|
||||
challenge_title = challenge.title
|
||||
game_title = challenge.game.title
|
||||
difficulty = challenge.difficulty
|
||||
|
||||
# Get active event for bonus calculation
|
||||
active_event = await event_service.get_active_event(db, marathon_id)
|
||||
|
||||
# Calculate base points (no streak bonus for event assignments)
|
||||
participant = assignment.participant
|
||||
challenge = assignment.challenge
|
||||
base_points = challenge.points
|
||||
|
||||
# Handle common enemy bonus
|
||||
common_enemy_bonus = 0
|
||||
common_enemy_closed = False
|
||||
@@ -1114,12 +1210,13 @@ async def complete_event_assignment(
|
||||
# Log activity
|
||||
activity_data = {
|
||||
"assignment_id": assignment.id,
|
||||
"game": challenge.game.title,
|
||||
"challenge": challenge.title,
|
||||
"difficulty": challenge.difficulty,
|
||||
"game": game_title,
|
||||
"challenge": challenge_title,
|
||||
"difficulty": difficulty,
|
||||
"points": total_points,
|
||||
"event_type": EventType.COMMON_ENEMY.value,
|
||||
"is_event_assignment": True,
|
||||
"is_playthrough": assignment.is_playthrough,
|
||||
}
|
||||
if common_enemy_bonus:
|
||||
activity_data["common_enemy_bonus"] = common_enemy_bonus
|
||||
|
||||
@@ -47,6 +47,8 @@ class EventService:
|
||||
created_by_id: int | None = None,
|
||||
duration_minutes: int | None = None,
|
||||
challenge_id: int | None = None,
|
||||
game_id: int | None = None,
|
||||
is_playthrough: bool = False,
|
||||
) -> Event:
|
||||
"""Start a new event"""
|
||||
# Check no active event
|
||||
@@ -63,8 +65,12 @@ class EventService:
|
||||
|
||||
# Build event data
|
||||
data = {}
|
||||
if event_type == EventType.COMMON_ENEMY.value and challenge_id:
|
||||
data["challenge_id"] = challenge_id
|
||||
if event_type == EventType.COMMON_ENEMY.value and (challenge_id or game_id):
|
||||
if is_playthrough and game_id:
|
||||
data["game_id"] = game_id
|
||||
data["is_playthrough"] = True
|
||||
else:
|
||||
data["challenge_id"] = challenge_id
|
||||
data["completions"] = [] # Track who completed and when
|
||||
|
||||
event = Event(
|
||||
@@ -79,9 +85,11 @@ class EventService:
|
||||
db.add(event)
|
||||
await db.flush() # Get event.id before committing
|
||||
|
||||
# Auto-assign challenge to all participants for Common Enemy
|
||||
if event_type == EventType.COMMON_ENEMY.value and challenge_id:
|
||||
await self._assign_common_enemy_to_all(db, marathon_id, event.id, challenge_id)
|
||||
# Auto-assign challenge/playthrough to all participants for Common Enemy
|
||||
if event_type == EventType.COMMON_ENEMY.value and (challenge_id or game_id):
|
||||
await self._assign_common_enemy_to_all(
|
||||
db, marathon_id, event.id, challenge_id, game_id, is_playthrough
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(event)
|
||||
@@ -105,7 +113,9 @@ class EventService:
|
||||
db: AsyncSession,
|
||||
marathon_id: int,
|
||||
event_id: int,
|
||||
challenge_id: int,
|
||||
challenge_id: int | None,
|
||||
game_id: int | None = None,
|
||||
is_playthrough: bool = False,
|
||||
) -> None:
|
||||
"""Create event assignments for all participants in the marathon"""
|
||||
# Get all participants
|
||||
@@ -118,7 +128,9 @@ class EventService:
|
||||
for participant in participants:
|
||||
assignment = Assignment(
|
||||
participant_id=participant.id,
|
||||
challenge_id=challenge_id,
|
||||
challenge_id=challenge_id if not is_playthrough else None,
|
||||
game_id=game_id if is_playthrough else None,
|
||||
is_playthrough=is_playthrough,
|
||||
status=AssignmentStatus.ACTIVE.value,
|
||||
event_type=EventType.COMMON_ENEMY.value,
|
||||
is_event_assignment=True,
|
||||
@@ -290,6 +302,30 @@ class EventService:
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_common_enemy_game(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
event: Event
|
||||
):
|
||||
"""Get the playthrough game for common enemy event (if it's a playthrough)"""
|
||||
from app.models import Game
|
||||
|
||||
if event.type != EventType.COMMON_ENEMY.value:
|
||||
return None
|
||||
|
||||
data = event.data or {}
|
||||
if not data.get("is_playthrough"):
|
||||
return None
|
||||
|
||||
game_id = data.get("game_id")
|
||||
if not game_id:
|
||||
return None
|
||||
|
||||
result = await db.execute(
|
||||
select(Game).where(Game.id == game_id)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
def get_time_remaining(self, event: Event | None) -> int | None:
|
||||
"""Get remaining time in seconds for an event"""
|
||||
if not event or not event.end_time:
|
||||
|
||||
Reference in New Issue
Block a user