refactor: move scoreboard promotion out of lobby view
All checks were successful
CI / test-and-quality (pull_request) Successful in 3m26s
CI / test-and-quality (push) Successful in 3m28s

This commit is contained in:
2026-03-17 10:41:09 +00:00
parent 7f20cb3bf9
commit a916da12a7
5 changed files with 134 additions and 35 deletions

View File

@@ -34,8 +34,10 @@ class LobbyGameplayExtractionTests(TestCase):
self.assertIs(lobby_views._select_round_question, gameplay_services.select_round_question)
self.assertIs(lobby_views._prepare_mixed_answers, gameplay_services.prepare_mixed_answers)
self.assertIs(lobby_views._resolve_scores, gameplay_services.resolve_scores)
self.assertIs(lobby_views._promote_reveal_to_scoreboard, gameplay_services.promote_reveal_to_scoreboard)
self.assertIs(lobby_views._start_next_round, gameplay_services.start_next_round)
self.assertIs(lobby_views._finish_game, gameplay_services.finish_game)
self.assertIs(lobby_views._build_scoreboard_phase_event, gameplay_payloads.build_scoreboard_phase_event)
self.assertIs(lobby_views._build_start_next_round_phase_event, gameplay_payloads.build_start_next_round_phase_event)
self.assertIs(lobby_views._build_start_next_round_response, gameplay_payloads.build_start_next_round_response)
self.assertIs(lobby_views._build_finish_game_phase_event, gameplay_payloads.build_finish_game_phase_event)

View File

@@ -15,6 +15,7 @@ from fupogfakta.payloads import (
build_leaderboard as _build_leaderboard,
build_lie_started_payload as _build_lie_started_payload,
build_reveal_payload as _build_reveal_payload,
build_scoreboard_phase_event as _build_scoreboard_phase_event,
build_start_next_round_phase_event as _build_start_next_round_phase_event,
build_start_next_round_response as _build_start_next_round_response,
)
@@ -22,6 +23,7 @@ from fupogfakta.services import (
finish_game as _finish_game,
get_current_round_question as _get_current_round_question,
prepare_mixed_answers as _prepare_mixed_answers,
promote_reveal_to_scoreboard as _promote_reveal_to_scoreboard,
resolve_scores as _resolve_scores,
select_round_question as _select_round_question,
start_next_round as _start_next_round,
@@ -73,38 +75,15 @@ def _create_unique_session_code() -> str:
def _maybe_promote_reveal_to_scoreboard(session: GameSession) -> GameSession:
if session.status != GameSession.Status.REVEAL:
return session
current_round_question = _get_current_round_question(session)
if current_round_question is None:
return session
players_count = Player.objects.filter(session=session).count()
guess_count = Guess.objects.filter(round_question=current_round_question).count()
has_score_events = ScoreEvent.objects.filter(
session=session,
meta__round_question_id=current_round_question.id,
).exists()
reveal_is_resolved = has_score_events or (players_count > 0 and guess_count >= players_count)
if not reveal_is_resolved:
return session
with transaction.atomic():
locked_session = GameSession.objects.select_for_update().get(pk=session.pk)
if locked_session.status != GameSession.Status.REVEAL:
return locked_session
locked_session.status = GameSession.Status.SCOREBOARD
locked_session.save(update_fields=["status"])
leaderboard = _build_leaderboard(session)
sync_broadcast_phase_event(
session.code,
"phase.scoreboard",
{"leaderboard": list(leaderboard), "current_round": session.current_round},
)
session.refresh_from_db(fields=["status"])
return session
transition = _promote_reveal_to_scoreboard(session)
if transition.should_broadcast:
phase_event = _build_scoreboard_phase_event(transition.session, transition.leaderboard)
sync_broadcast_phase_event(
transition.session.code,
phase_event["name"],
phase_event["payload"],
)
return transition.session
@@ -290,7 +269,7 @@ def session_detail(request: HttpRequest, code: str) -> JsonResponse:
"reveal": _build_reveal_payload(current_round_question)
if session.status in {GameSession.Status.REVEAL, GameSession.Status.SCOREBOARD} and current_round_question
else None,
"scoreboard": _build_leaderboard(session)
"scoreboard": _build_scoreboard_phase_event(session)["payload"]["leaderboard"]
if session.status in {GameSession.Status.SCOREBOARD, GameSession.Status.FINISHED}
else None,
"phase_view_model": phase_view_model,
@@ -893,11 +872,19 @@ def reveal_scoreboard(request: HttpRequest, code: str) -> JsonResponse:
if session.host_id != request.user.id:
return api_error(request, code="host_only_view_scoreboard", status=403)
session = _maybe_promote_reveal_to_scoreboard(session)
transition = _promote_reveal_to_scoreboard(session)
if transition.should_broadcast:
phase_event = _build_scoreboard_phase_event(transition.session, transition.leaderboard)
sync_broadcast_phase_event(
transition.session.code,
phase_event["name"],
phase_event["payload"],
)
session = transition.session
if session.status not in {GameSession.Status.SCOREBOARD, GameSession.Status.FINISHED}:
return api_error(request, code="scoreboard_invalid_phase", status=400)
leaderboard = _build_leaderboard(session)
leaderboard = transition.leaderboard
return JsonResponse(
{