import random from django.contrib.auth.decorators import login_required from django.middleware.csrf import get_token from django.http import HttpRequest, JsonResponse from django.views.decorators.http import require_GET, require_POST from fupogfakta.models import GameSession, Player from fupogfakta.payloads import ( SessionViewerRole, build_session_detail_gameplay_payload as _build_session_detail_gameplay_payload, build_session_players_payload as _build_session_players_payload, ) from fupogfakta.services import get_current_round_question as _get_current_round_question from fupogfakta.views import maybe_promote_reveal_to_scoreboard as _maybe_promote_reveal_to_scoreboard from voice.services import resolve_session_voice_cues from .http import json_body, normalize_session_code from .i18n import api_error SESSION_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" SESSION_CODE_LENGTH = 6 MAX_CODE_GENERATION_ATTEMPTS = 20 JOINABLE_STATUSES = { GameSession.Status.LOBBY, GameSession.Status.LIE, GameSession.Status.GUESS, GameSession.Status.REVEAL, GameSession.Status.SCOREBOARD, } def _generate_session_code() -> str: return "".join(random.choices(SESSION_CODE_ALPHABET, k=SESSION_CODE_LENGTH)) def _create_unique_session_code() -> str: for _ in range(MAX_CODE_GENERATION_ATTEMPTS): code = _generate_session_code() if not GameSession.objects.filter(code=code).exists(): return code raise RuntimeError("Could not generate unique session code") @require_GET def csrf_token(request: HttpRequest) -> JsonResponse: return JsonResponse({"csrf_token": get_token(request)}) @require_POST @login_required def create_session(request: HttpRequest) -> JsonResponse: code = _create_unique_session_code() session = GameSession.objects.create(host=request.user, code=code) return JsonResponse( { "session": { "code": session.code, "status": session.status, "host_id": session.host_id, "current_round": session.current_round, } }, status=201, ) @require_POST def join_session(request: HttpRequest) -> JsonResponse: payload = json_body(request) code = normalize_session_code(str(payload.get("code", ""))) nickname = str(payload.get("nickname", "")).strip() if not code: return api_error( request, code="session_code_required", status=400, ) if len(nickname) < 2 or len(nickname) > 40: return api_error( request, code="nickname_invalid", status=400, ) try: session = GameSession.objects.get(code=code) except GameSession.DoesNotExist: return api_error( request, code="session_not_found", status=404, ) if session.status not in JOINABLE_STATUSES: return api_error( request, code="session_not_joinable", status=400, ) if Player.objects.filter(session=session, nickname__iexact=nickname).exists(): return api_error( request, code="nickname_taken", status=409, ) player = Player.objects.create(session=session, nickname=nickname) return JsonResponse( { "player": { "id": player.id, "nickname": player.nickname, "session_token": player.session_token, "score": player.score, }, "session": { "code": session.code, "status": session.status, }, }, status=201, ) def _resolve_session_detail_viewer_role(request: HttpRequest, session: GameSession) -> SessionViewerRole: if request.user.is_authenticated and request.user.id == session.host_id: return "host" session_token = str(request.GET.get("session_token", "")).strip() if session_token and Player.objects.filter(session=session, session_token=session_token).exists(): return "player" return "public" @require_GET def session_detail(request: HttpRequest, code: str) -> JsonResponse: session_code = normalize_session_code(code) try: session = GameSession.objects.get(code=session_code) except GameSession.DoesNotExist: return api_error( request, code="session_not_found", status=404, ) players = _build_session_players_payload(session) session = _maybe_promote_reveal_to_scoreboard(session) current_round_question = _get_current_round_question(session) viewer_role = _resolve_session_detail_viewer_role(request, session) gameplay_payload = _build_session_detail_gameplay_payload( session, current_round_question=current_round_question, players_count=len(players), viewer_role=viewer_role, voice_cues=resolve_session_voice_cues(session, current_round_question=current_round_question), ) return JsonResponse( { "session": { "code": session.code, "status": session.status, "host_id": session.host_id, "current_round": session.current_round, "players_count": len(players), }, "viewer_role": viewer_role, "players": players, **gameplay_payload, } )