183 lines
5.4 KiB
Python
183 lines
5.4 KiB
Python
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,
|
|
}
|
|
)
|