Serve static assets with Granian and add YAML-configurable logging

Granian serves the compiled SPA assets directly in Rust: hashed js/wasm/css
under /static (the release build uses --public-url /static/) and the card
images under /assets, configured with the GRANIAN_STATIC_PATH_ROUTE/MOUNT/
DIR_TO_FILE env vars in the Dockerfile. The Python catch-all now only serves
the SPA shell (index.html) at / and for client-side routes.

Every module logs through getLogger(__name__): lifecycle and business events
at INFO, per-move and store detail at DEBUG. The built-in default writes
DEBUG to the console; LOGGING_CONFIG points at a YAML file in the
logging.config.dictConfig schema to take over the configuration. PyYAML
becomes a direct dependency.
This commit is contained in:
2026-09-17 08:26:45 +08:00
parent f6239d2637
commit 876b4abd8b
17 changed files with 336 additions and 79 deletions
+7
View File
@@ -25,12 +25,15 @@ import asyncio
import contextlib
import json
from abc import ABC, abstractmethod
from logging import getLogger
from typing import AsyncContextManager, AsyncIterator, Dict, Optional, Set
from redis.asyncio import Redis
from .game.state import GameState
log = getLogger(__name__)
GAME_KEY_PREFIX = "tavolo:game:"
CODE_KEY_PREFIX = "tavolo:code:"
CHANNEL_PREFIX = "tavolo:game:"
@@ -86,9 +89,11 @@ class RedisGameStore(GameStore):
raw = await self._redis.get(f"{GAME_KEY_PREFIX}{game_id}")
if raw is None:
log.debug("redis load %s: miss", game_id)
return None
if isinstance(raw, bytes):
raw = raw.decode("utf-8")
log.debug("redis load %s: hit", game_id)
return GameState.from_json(json.loads(raw))
async def save(self, state: GameState) -> None:
@@ -98,6 +103,7 @@ class RedisGameStore(GameStore):
pipe.set(f"{GAME_KEY_PREFIX}{state.id}", payload, ex=self._ttl)
pipe.set(f"{CODE_KEY_PREFIX}{state.join_code}", state.id, ex=self._ttl)
await pipe.execute()
log.debug("redis save %s (phase %s, ttl %ds)", state.id, state.phase, self._ttl)
async def find_by_code(self, code: str) -> Optional[GameState]:
game_id = await self._redis.get(f"{CODE_KEY_PREFIX}{code.upper()}")
@@ -120,6 +126,7 @@ class RedisGameStore(GameStore):
async def publish(self, game_id: str) -> None:
await self._redis.publish(_channel(game_id), "update")
log.debug("redis publish %s", game_id)
async def _redis_events(pubsub) -> AsyncIterator[None]: