feat(i18n): share lobby message catalog across frontend/backend
This commit is contained in:
@@ -20,6 +20,8 @@ from fupogfakta.models import (
|
||||
ScoreEvent,
|
||||
)
|
||||
|
||||
from .i18n import api_error, lobby_i18n_errors
|
||||
|
||||
SESSION_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
||||
SESSION_CODE_LENGTH = 6
|
||||
MAX_CODE_GENERATION_ATTEMPTS = 20
|
||||
@@ -29,6 +31,7 @@ JOINABLE_STATUSES = {
|
||||
GameSession.Status.GUESS,
|
||||
GameSession.Status.REVEAL,
|
||||
}
|
||||
ERROR_CODES = lobby_i18n_errors()
|
||||
|
||||
|
||||
def _json_body(request: HttpRequest) -> dict:
|
||||
@@ -124,21 +127,41 @@ def join_session(request: HttpRequest) -> JsonResponse:
|
||||
nickname = str(payload.get("nickname", "")).strip()
|
||||
|
||||
if not code:
|
||||
return JsonResponse({"error": "Session code is required"}, status=400)
|
||||
return api_error(
|
||||
code=ERROR_CODES.get("session_code_required", "session_code_required"),
|
||||
message="Session code is required",
|
||||
status=400,
|
||||
)
|
||||
|
||||
if len(nickname) < 2 or len(nickname) > 40:
|
||||
return JsonResponse({"error": "Nickname must be between 2 and 40 characters"}, status=400)
|
||||
return api_error(
|
||||
code=ERROR_CODES.get("nickname_invalid", "nickname_invalid"),
|
||||
message="Nickname must be between 2 and 40 characters",
|
||||
status=400,
|
||||
)
|
||||
|
||||
try:
|
||||
session = GameSession.objects.get(code=code)
|
||||
except GameSession.DoesNotExist:
|
||||
return JsonResponse({"error": "Session not found"}, status=404)
|
||||
return api_error(
|
||||
code=ERROR_CODES.get("session_not_found", "session_not_found"),
|
||||
message="Session not found",
|
||||
status=404,
|
||||
)
|
||||
|
||||
if session.status not in JOINABLE_STATUSES:
|
||||
return JsonResponse({"error": "Session is not joinable"}, status=400)
|
||||
return api_error(
|
||||
code=ERROR_CODES.get("session_not_joinable", "session_not_joinable"),
|
||||
message="Session is not joinable",
|
||||
status=400,
|
||||
)
|
||||
|
||||
if Player.objects.filter(session=session, nickname__iexact=nickname).exists():
|
||||
return JsonResponse({"error": "Nickname already taken"}, status=409)
|
||||
return api_error(
|
||||
code=ERROR_CODES.get("nickname_taken", "nickname_taken"),
|
||||
message="Nickname already taken",
|
||||
status=409,
|
||||
)
|
||||
|
||||
player = Player.objects.create(session=session, nickname=nickname)
|
||||
|
||||
@@ -166,7 +189,11 @@ def session_detail(request: HttpRequest, code: str) -> JsonResponse:
|
||||
try:
|
||||
session = GameSession.objects.get(code=session_code)
|
||||
except GameSession.DoesNotExist:
|
||||
return JsonResponse({"error": "Session not found"}, status=404)
|
||||
return api_error(
|
||||
code=ERROR_CODES.get("session_not_found", "session_not_found"),
|
||||
message="Session not found",
|
||||
status=404,
|
||||
)
|
||||
|
||||
players = list(
|
||||
session.players.order_by("nickname").values(
|
||||
@@ -223,25 +250,41 @@ def start_round(request: HttpRequest, code: str) -> JsonResponse:
|
||||
category_slug = str(payload.get("category_slug", "")).strip()
|
||||
|
||||
if not category_slug:
|
||||
return JsonResponse({"error": "category_slug is required"}, status=400)
|
||||
return api_error(
|
||||
code=ERROR_CODES.get("category_slug_required", "category_slug_required"),
|
||||
message="category_slug is required",
|
||||
status=400,
|
||||
)
|
||||
|
||||
session_code = _normalize_session_code(code)
|
||||
|
||||
try:
|
||||
session = GameSession.objects.get(code=session_code)
|
||||
except GameSession.DoesNotExist:
|
||||
return JsonResponse({"error": "Session not found"}, status=404)
|
||||
return api_error(
|
||||
code=ERROR_CODES.get("session_not_found", "session_not_found"),
|
||||
message="Session not found",
|
||||
status=404,
|
||||
)
|
||||
|
||||
if session.host_id != request.user.id:
|
||||
return JsonResponse({"error": "Only host can start round"}, status=403)
|
||||
|
||||
if session.status != GameSession.Status.LOBBY:
|
||||
return JsonResponse({"error": "Round can only be started from lobby"}, status=400)
|
||||
return api_error(
|
||||
code=ERROR_CODES.get("round_start_invalid_phase", "round_start_invalid_phase"),
|
||||
message="Round can only be started from lobby",
|
||||
status=400,
|
||||
)
|
||||
|
||||
try:
|
||||
category = Category.objects.get(slug=category_slug, is_active=True)
|
||||
except Category.DoesNotExist:
|
||||
return JsonResponse({"error": "Category not found"}, status=404)
|
||||
return api_error(
|
||||
code=ERROR_CODES.get("category_not_found", "category_not_found"),
|
||||
message="Category not found",
|
||||
status=404,
|
||||
)
|
||||
|
||||
if not Question.objects.filter(category=category, is_active=True).exists():
|
||||
return JsonResponse({"error": "Category has no active questions"}, status=400)
|
||||
@@ -249,7 +292,11 @@ def start_round(request: HttpRequest, code: str) -> JsonResponse:
|
||||
with transaction.atomic():
|
||||
session = GameSession.objects.select_for_update().get(pk=session.pk)
|
||||
if session.status != GameSession.Status.LOBBY:
|
||||
return JsonResponse({"error": "Round can only be started from lobby"}, status=400)
|
||||
return api_error(
|
||||
code=ERROR_CODES.get("round_start_invalid_phase", "round_start_invalid_phase"),
|
||||
message="Round can only be started from lobby",
|
||||
status=400,
|
||||
)
|
||||
|
||||
round_config, created = RoundConfig.objects.get_or_create(
|
||||
session=session,
|
||||
@@ -257,7 +304,11 @@ def start_round(request: HttpRequest, code: str) -> JsonResponse:
|
||||
defaults={"category": category},
|
||||
)
|
||||
if not created:
|
||||
return JsonResponse({"error": "Round already configured"}, status=409)
|
||||
return api_error(
|
||||
code=ERROR_CODES.get("round_already_configured", "round_already_configured"),
|
||||
message="Round already configured",
|
||||
status=409,
|
||||
)
|
||||
|
||||
session.status = GameSession.Status.LIE
|
||||
session.save(update_fields=["status"])
|
||||
|
||||
Reference in New Issue
Block a user