Switch to Svelte

This commit is contained in:
2026-06-11 14:39:41 -07:00
parent 29860061c4
commit 58a7e4d4f1
74 changed files with 7144 additions and 2379 deletions

View File

@@ -1,49 +1,37 @@
from typing import Optional
from fastapi import FastAPI, Request, Form, Depends, HTTPException, status
from fastapi.responses import RedirectResponse, HTMLResponse
from fastapi import FastAPI, Request, Form, Depends, HTTPException, status, APIRouter
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from sqlmodel import Session, select
import uvicorn
import os
from pathlib import Path
from . import crud
from .database import create_db_and_tables, get_session
from .templates import templates, BASE_DIR
from .models import GameEvent
app = FastAPI(title="Rats with Gats Remote Play")
BASE_DIR = Path(__file__).parent
# Mount static files
app.mount("/static", StaticFiles(directory=BASE_DIR / "static"), name="static")
app = FastAPI(title="Rats with Gats Remote Play API")
api = APIRouter()
# Register startup event to create tables
@app.on_event("startup")
def on_startup():
create_db_and_tables()
# --- HTTP Core Route Handlers ---
# --- API Core Route Handlers ---
@app.get("/", response_class=HTMLResponse)
def home(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
@app.post("/game/create")
@api.post("/game")
def create_game_route(
crew_name: str = Form(...),
db: Session = Depends(get_session)
):
game = crud.create_game(db, crew_name=crew_name.strip())
# Redirect creator to the join page with creator flag
return RedirectResponse(url=f"/game/{game.id}/join?creator=true", status_code=status.HTTP_303_SEE_OTHER)
return {"id": game.id}
@app.get("/game/{game_id}/join", response_class=HTMLResponse)
def join_game_form(request: Request, game_id: str, creator: bool = False, db: Session = Depends(get_session)):
game = crud.get_game(db, game_id)
if not game:
raise HTTPException(status_code=404, detail="Game not found")
return templates.TemplateResponse("join_form.html", {"request": request, "game": game, "creator": creator})
@app.post("/game/{game_id}/join")
@api.post("/game/{game_id}/join")
def join_game_submit(
game_id: str,
name: str = Form(...),
@@ -56,80 +44,46 @@ def join_game_submit(
# First player is automatically the creator
is_creator = len(game.players) == 0
player = crud.add_player(db, game_id, name.strip(), is_creator=is_creator)
return RedirectResponse(url=f"/game/{game_id}/player/{player.id}", status_code=status.HTTP_303_SEE_OTHER)
return {"id": player.id}
@app.get("/game/{game_id}/player/{player_id}", response_class=HTMLResponse)
def player_dashboard(request: Request, game_id: str, player_id: str, db: Session = Depends(get_session)):
@api.get("/game/{game_id}/player/{player_id}/state")
def get_game_state(game_id: str, player_id: str, db: Session = Depends(get_session)):
game = crud.get_game(db, game_id)
player = crud.get_player(db, player_id)
if not game or not player:
raise HTTPException(status_code=404, detail="Game or Player not found")
return templates.TemplateResponse("dashboard.html", {
"request": request,
"game": game,
"player": player
})
# --- HTMX Core Polling Route ---
@app.get("/game/{game_id}/player/{player_id}/phase-check")
def phase_check_route(game_id: str, player_id: str, current: str, rank: Optional[int] = None, needs_name: Optional[bool] = None, hand_len: Optional[int] = None, db: Session = Depends(get_session)):
game = crud.get_game(db, game_id)
if not game:
raise HTTPException(status_code=404, detail="Game not found")
player = crud.get_player(db, player_id)
if not player:
raise HTTPException(status_code=404, detail="Player not found")
state_changed = False
if game.phase != current:
state_changed = True
elif rank is not None and player.rank != rank:
state_changed = True
elif needs_name is not None and player.needs_name != needs_name:
state_changed = True
elif hand_len is not None:
import json
current_hand = json.loads(player.hand_cards)
if len(current_hand) != hand_len:
state_changed = True
if state_changed:
# Phase or state changed! Trigger page reload on client via HTMX header
return HTMLResponse(status_code=200, headers={"HX-Trigger": "phase-changed"})
return HTMLResponse(status_code=204) # No content, no reload
@app.get("/game/{game_id}/player/{player_id}/events", response_class=HTMLResponse)
def get_events_snippet(request: Request, game_id: str, player_id: str, db: Session = Depends(get_session)):
game = crud.get_game(db, game_id)
if not game:
raise HTTPException(status_code=404, detail="Game not found")
events = db.exec(select(GameEvent).where(GameEvent.game_id == game_id).order_by(GameEvent.timestamp.asc())).all()
return templates.TemplateResponse("event_log_snippet.html", {
"request": request,
"events": events
})
return {
"game": game.model_dump(),
"player": player.model_dump(),
"players": [p.model_dump() for p in game.players],
"obstacles": [o.model_dump() for o in game.obstacles],
"votes": [v.model_dump() for v in game.votes],
"events": [e.model_dump() for e in events],
}
# --- Register Modular Phase Routers ---
from .phase_view import router as phase_view_router
from .routes_lobby import router as lobby_router
from .routes_character import router as character_router
from .routes_scene import router as scene_router
from .routes_upkeep import router as upkeep_router
from .routes_admin import router as admin_router
app.include_router(phase_view_router)
app.include_router(lobby_router)
app.include_router(character_router)
app.include_router(scene_router)
app.include_router(upkeep_router)
app.include_router(admin_router)
api.include_router(lobby_router)
api.include_router(character_router)
api.include_router(scene_router)
api.include_router(upkeep_router)
api.include_router(admin_router)
# Mount API at /api
app.include_router(api, prefix="/api")
# Mount SPA
static_dir = BASE_DIR / "static"
if os.path.exists(static_dir):
app.mount("/", StaticFiles(directory=static_dir, html=True), name="static")
def main():
import argparse