Vanilla JS uden build-trin: én WebSocket pr. skærm med automatisk genforbindelse, tegner altid fra seneste snapshot. Storskærmen viser kode, QR, nedtælling og en animeret afsløring; telefonen er bygget til store knapper og genoptager sin plads efter genindlæsning. dev.html viser alle faser uden server. deploy/ har systemd-unit, deploy-script og en bot-test, der spiller et helt spil mod en kørende server. Claude-Session: https://claude.ai/code/session_01JmhJG6JnhXfzNiKv8f2vHu
69 lines
2.0 KiB
Python
69 lines
2.0 KiB
Python
import io
|
|
import re
|
|
|
|
import segno
|
|
from asgiref.sync import sync_to_async
|
|
from django.conf import settings
|
|
from django.http import Http404, HttpResponse, HttpResponseForbidden
|
|
from django.shortcuts import redirect, render
|
|
from django.views.decorators.http import require_GET, require_POST
|
|
|
|
from fupogfakta.models import load_game_questions
|
|
|
|
from .rooms import create_room, get_room
|
|
|
|
CODE_RE = re.compile(r"^[A-Za-z]{4}$")
|
|
|
|
|
|
def _forbidden():
|
|
return HttpResponseForbidden("Du skal være logget ind som vært for at oprette et spil.")
|
|
|
|
|
|
@require_GET
|
|
def play(request):
|
|
code = request.GET.get("code", "")
|
|
return render(request, "party/play.html", {"prefill_code": code.upper() if CODE_RE.match(code) else ""})
|
|
|
|
|
|
@require_GET
|
|
def host_new_page(request):
|
|
if not request.host_identity:
|
|
return _forbidden()
|
|
return render(request, "party/host_new.html", {"username": request.host_identity.username})
|
|
|
|
|
|
@require_POST
|
|
async def host_new(request):
|
|
ident = request.host_identity
|
|
if not ident:
|
|
return _forbidden()
|
|
questions = await sync_to_async(load_game_questions)()
|
|
if not questions:
|
|
return HttpResponse("Der er ingen spørgsmål i banken. Kør 'manage.py load_questions'.", status=503)
|
|
room = create_room(questions, ident.username, settings.PUBLIC_URL)
|
|
return redirect("party:host", code=room.code)
|
|
|
|
|
|
@require_GET
|
|
def host(request, code):
|
|
if not request.host_identity:
|
|
return _forbidden()
|
|
if not get_room(code):
|
|
return redirect("party:host_new_page")
|
|
return render(request, "party/host.html", {"code": code.upper()})
|
|
|
|
|
|
@require_GET
|
|
def qr(request, code):
|
|
if not CODE_RE.match(code):
|
|
raise Http404
|
|
buf = io.BytesIO()
|
|
segno.make(f"{settings.PUBLIC_URL}/?code={code.upper()}", error="m").save(
|
|
buf, kind="svg", scale=10, border=2, dark="#1b1035", light="#ffffff")
|
|
return HttpResponse(buf.getvalue(), content_type="image/svg+xml")
|
|
|
|
|
|
@require_GET
|
|
def healthz(request):
|
|
return HttpResponse("ok", content_type="text/plain")
|