feature/lobby-mvp: automated PR #1

Closed
email-manager wants to merge 7 commits from feature/lobby-mvp into main
10 changed files with 187 additions and 30 deletions

37
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,37 @@
name: CI
on:
pull_request:
push:
branches: [ main, 'feature/**', 'release/**' ]
jobs:
test-and-quality:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
pip install pytest pytest-cov ruff black mypy
- name: Lint
run: ruff check .
- name: Format check
run: black --check .
- name: Type check
run: mypy . || true
- name: Tests + coverage
run: |
pytest --maxfail=1 --disable-warnings --cov=. --cov-report=term-missing --cov-report=xml --cov-fail-under=70

15
TODO.md
View File

@@ -108,3 +108,18 @@ Byg **Weirsøe Party Protocol**: en dansk party-webapp platform ala Jackbox, hvo
- [ ] (Need-to-have) Audit-log for host-handlinger (start/stop/skip)
- [ ] (Nice-to-have) Runde-tema musik/lyd-cues
- [ ] (Nice-to-have) Hurtig onboarding-skærm for nye spillere
### Fase 11 — i18n (email-manager model)
- [ ] Sæt LANGUAGES op med dev (jank-english), en, da
- [ ] Tilføj LocaleMiddleware + LOCALE_PATHS
- [ ] Brug `{% load i18n %}` i templates + gettext i Python
- [ ] Opret .po for en og da (dev beholdes som udviklingssprog)
- [ ] Tilføj make-targets/kommandoer for makemessages og compilemessages
- [ ] Tilføj test der sikrer i18n tags i templates (inspireret af email-manager)
### Fase 12 — CI/CD og merge-gates (Gitea)
- [ ] Opret CI-workflow i .gitea/workflows/ci.yml
- [ ] Kør lint, format, tests, coverage i CI
- [ ] Enforce coverage >= 70%
- [ ] Branch protection på main (kræv grøn CI + review)
- [ ] Tilføj quality gate-dokumentation i docs/QUALITY_GATES.md

8
coordination/README.md Normal file
View File

@@ -0,0 +1,8 @@
# Coordination
Denne mappe bruges af scheduler/dev-runners til at holde styr på:
- hvem der ejer hvilken opgave
- hvilken branch der er aktiv
- hvad der står i queue
Single source of truth: `assignments.json`.

View File

@@ -0,0 +1,5 @@
{
"updatedAt": "2026-02-27T00:00:00Z",
"active": [],
"queue": []
}

View File

@@ -0,0 +1,26 @@
{
"updatedAt": "2026-02-27T00:00:00Z",
"tasks": [
{
"id": "BOT-PR-POLICY",
"title": "Dev-runner skal oprette PR ved feature-ready",
"ownerRole": "job-scheduler",
"status": "active",
"priority": "high"
},
{
"id": "BOT-REVIEW-ONLY-PRS",
"title": "Review-runner reviewer kun åbne PRs og commenter i PR",
"ownerRole": "review-runner",
"status": "active",
"priority": "high"
},
{
"id": "BOT-MERGE-GATE",
"title": "Integrator-runner merger kun ved grønne gates",
"ownerRole": "integrator-runner",
"status": "active",
"priority": "high"
}
]
}

View File

@@ -1,27 +0,0 @@
# Arkitektur (MVP)
## Moduler
- `core_admin`: global drift/admin, health, valideringer
- `lobby`: session creation/join, player presence
- `fupogfakta`: game rules, rounds, scoring (spil 1)
- `realtime`: websocket events + state sync
- `voice`: fælles voice-acting/TTS interface
## Auth & sessions
- Login (username/password) kræves for at oprette/hoste spil
- Deltagelse i kørende spil sker via session-kode
## Voice-acting (platformkrav)
- Alle spil skal kunne afspille voice lines via fælles interface
- Voice er modulært pr. spil (ikke hardcoded)
## Realtidsmodel
- Host-screen og mobilklienter forbinder via websocket
- Autoritativ game state ligger server-side
- Klienter sender intents (`submit_lie`, `submit_guess`)
- Server broadcaster state transitions
## Datamodel-principper
- Score beregnes server-side
- Hver scoreændring gemmes i `ScoreEvent`
- Runde-konfiguration gemmes per session (points ikke hardcoded)

8
docs/README.md Normal file
View File

@@ -0,0 +1,8 @@
# Documentation moved to Wiki
Projekt-dokumentation vedligeholdes i repo-wiki:
- Wiki repo: `weirsoe-party-protocol.wiki`
- Gitea wiki URL: `https://gitea.weircon.dk/wpp/weirsoe-party-protocol/wiki`
Denne `docs/` mappe holdes minimal fremover.

8
lobby/urls.py Normal file
View File

@@ -0,0 +1,8 @@
from django.urls import path
from . import views
urlpatterns = [
path('create', views.create_session, name='lobby-create-session'),
path('join', views.join_session, name='lobby-join-session'),
path('state/<str:code>', views.session_state, name='lobby-session-state'),
]

View File

@@ -1,3 +1,79 @@
from django.shortcuts import render
import random
import string
import json
# Create your views here.
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.views.decorators.http import require_GET, require_POST
from django.views.decorators.csrf import csrf_exempt
from fupogfakta.models import GameSession, Player
def _session_code(length: int = 6) -> str:
alphabet = string.ascii_uppercase + string.digits
for _ in range(20):
code = ''.join(random.choice(alphabet) for _ in range(length))
if not GameSession.objects.filter(code=code).exists():
return code
raise RuntimeError('Kunne ikke generere unik session-kode')
@login_required
@require_POST
def create_session(request):
session = GameSession.objects.create(host=request.user, code=_session_code())
return JsonResponse({'ok': True, 'session': {'code': session.code, 'status': session.status}})
@csrf_exempt
@require_POST
def join_session(request):
try:
payload = json.loads(request.body.decode('utf-8'))
except Exception:
payload = {}
code = (payload.get('code') or '').strip().upper()
nickname = (payload.get('nickname') or '').strip()
if not code or not nickname:
return JsonResponse({'ok': False, 'error': 'Mangler code eller nickname'}, status=400)
try:
session = GameSession.objects.get(code=code)
except GameSession.DoesNotExist:
return JsonResponse({'ok': False, 'error': 'Ugyldig session-kode'}, status=404)
if session.status == GameSession.Status.FINISHED:
return JsonResponse({'ok': False, 'error': 'Spillet er afsluttet'}, status=409)
player, _created = Player.objects.get_or_create(session=session, nickname=nickname)
player.is_connected = True
player.save(update_fields=['is_connected'])
return JsonResponse({
'ok': True,
'player': {'id': player.id, 'nickname': player.nickname},
'session': {'code': session.code, 'status': session.status},
})
@require_GET
def session_state(_request, code: str):
code = code.strip().upper()
try:
session = GameSession.objects.get(code=code)
except GameSession.DoesNotExist:
return JsonResponse({'ok': False, 'error': 'Ugyldig session-kode'}, status=404)
players = list(session.players.values('id', 'nickname', 'score', 'is_connected'))
return JsonResponse({
'ok': True,
'session': {
'code': session.code,
'status': session.status,
'current_round': session.current_round,
},
'players': players,
})

View File

@@ -1,6 +1,6 @@
from django.contrib import admin
from django.http import JsonResponse
from django.urls import path
from django.urls import path, include
def health(_request):
@@ -8,6 +8,7 @@ def health(_request):
urlpatterns = [
path('api/lobby/', include('lobby.urls')) ,
path('admin/', admin.site.urls),
path('healthz', health, name='healthz'),
]