feature/lobby-mvp: automated PR #1
37
.gitea/workflows/ci.yml
Normal file
37
.gitea/workflows/ci.yml
Normal 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
15
TODO.md
@@ -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
8
coordination/README.md
Normal 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`.
|
||||
5
coordination/assignments.json
Normal file
5
coordination/assignments.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"updatedAt": "2026-02-27T00:00:00Z",
|
||||
"active": [],
|
||||
"queue": []
|
||||
}
|
||||
26
coordination/scheduler_tasks.json
Normal file
26
coordination/scheduler_tasks.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
8
docs/README.md
Normal 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
8
lobby/urls.py
Normal 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'),
|
||||
]
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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'),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user