Files
weirsoe-party-protocol/fupogfakta/views.py

177 lines
6.0 KiB
Python

import json
import random
from datetime import timedelta
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.contrib.auth.decorators import login_required
from django.db import transaction
from django.http import HttpRequest, JsonResponse
from django.utils import timezone
from django.views.decorators.http import require_POST
from .models import GameSession, LieAnswer, Player, Question, RoundConfig, RoundQuestion
MAX_LIE_LENGTH = 255
def _json_body(request: HttpRequest) -> dict:
if not request.body:
return {}
try:
return json.loads(request.body)
except json.JSONDecodeError:
return {}
def _emit_realtime_session_event(session_code: str, event: str, payload: dict) -> None:
channel_layer = get_channel_layer()
if not channel_layer:
return
try:
async_to_sync(channel_layer.group_send)(
f"session_{session_code}",
{
"type": "session.event",
"event": event,
"payload": payload,
},
)
except Exception:
# Realtime broadcasting must not fail game flow.
return
@require_POST
@login_required
def start_lie_question(request: HttpRequest, code: str) -> JsonResponse:
session_code = code.strip().upper()
try:
session = GameSession.objects.get(code=session_code)
except GameSession.DoesNotExist:
return JsonResponse({"error": "Session not found"}, status=404)
if session.host_id != request.user.id:
return JsonResponse({"error": "Only host can start lie question"}, status=403)
if session.status != GameSession.Status.LIE:
return JsonResponse({"error": "Session is not in lie phase"}, status=400)
with transaction.atomic():
session = GameSession.objects.select_for_update().get(pk=session.pk)
if session.status != GameSession.Status.LIE:
return JsonResponse({"error": "Session is not in lie phase"}, status=400)
if RoundQuestion.objects.filter(session=session, round_number=session.current_round).exists():
return JsonResponse({"error": "Lie question already started for round"}, status=409)
try:
round_config = RoundConfig.objects.select_related("category").get(
session=session,
number=session.current_round,
)
except RoundConfig.DoesNotExist:
return JsonResponse({"error": "Round config not found"}, status=404)
question_qs = Question.objects.filter(category=round_config.category, is_active=True)
question_ids = list(question_qs.values_list("id", flat=True))
if not question_ids:
return JsonResponse({"error": "No active question for category"}, status=400)
question = question_qs.get(pk=random.choice(question_ids))
lie_deadline_at = timezone.now() + timedelta(seconds=round_config.lie_seconds)
round_question = RoundQuestion.objects.create(
session=session,
round_number=session.current_round,
question=question,
correct_answer=question.correct_answer,
lie_deadline_at=lie_deadline_at,
)
payload = {
"round": round_question.round_number,
"question_id": round_question.id,
"prompt": round_question.question.prompt,
"lie_deadline_at": round_question.lie_deadline_at.isoformat(),
"lie_seconds": round_config.lie_seconds,
}
_emit_realtime_session_event(session.code, "lie_question_started", payload)
return JsonResponse({"question": payload}, status=201)
@require_POST
def submit_lie(request: HttpRequest, code: str) -> JsonResponse:
session_code = code.strip().upper()
payload = _json_body(request)
player_id = payload.get("player_id")
lie_text = str(payload.get("text", "")).strip()
if not player_id:
return JsonResponse({"error": "player_id is required"}, status=400)
if len(lie_text) < 2 or len(lie_text) > MAX_LIE_LENGTH:
return JsonResponse({"error": "text must be between 2 and 255 characters"}, status=400)
try:
session = GameSession.objects.get(code=session_code)
except GameSession.DoesNotExist:
return JsonResponse({"error": "Session not found"}, status=404)
if session.status != GameSession.Status.LIE:
return JsonResponse({"error": "Session is not in lie phase"}, status=400)
try:
player = Player.objects.get(id=player_id, session=session)
except Player.DoesNotExist:
return JsonResponse({"error": "Player not found in session"}, status=404)
try:
round_question = RoundQuestion.objects.get(session=session, round_number=session.current_round)
except RoundQuestion.DoesNotExist:
return JsonResponse({"error": "No active lie question"}, status=404)
now = timezone.now()
if round_question.lie_deadline_at and now > round_question.lie_deadline_at:
return JsonResponse({"error": "Lie timer expired"}, status=400)
lie_answer, _ = LieAnswer.objects.update_or_create(
round_question=round_question,
player=player,
defaults={"text": lie_text},
)
submitted_count = LieAnswer.objects.filter(round_question=round_question).count()
players_count = session.players.count()
event_payload = {
"round": session.current_round,
"question_id": round_question.id,
"player_id": player.id,
"submitted_count": submitted_count,
"players_count": players_count,
"lie_deadline_at": round_question.lie_deadline_at.isoformat() if round_question.lie_deadline_at else None,
}
_emit_realtime_session_event(session.code, "lie_submitted", event_payload)
return JsonResponse(
{
"lie": {
"id": lie_answer.id,
"player_id": player.id,
"question_id": round_question.id,
},
"progress": {
"submitted_count": submitted_count,
"players_count": players_count,
},
},
status=201,
)