Skip to content

Commit ae1aa76

Browse files
Copilotmadebygps
andcommitted
Use Starlette SessionMiddleware instead of manual cookie handling
Co-authored-by: madebygps <6733686+madebygps@users.noreply.github.com>
1 parent c437482 commit ae1aa76

3 files changed

Lines changed: 23 additions & 44 deletions

File tree

app/main.py

Lines changed: 21 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,99 +2,77 @@
22

33
import uuid
44
from pathlib import Path
5-
from typing import Any
65

76
from fastapi import FastAPI, Request, Response
87
from fastapi.responses import HTMLResponse
98
from fastapi.staticfiles import StaticFiles
109
from fastapi.templating import Jinja2Templates
10+
from starlette.middleware.sessions import SessionMiddleware
1111

12-
from app.game_service import GameSession, get_session
12+
from app.game_service import get_session
1313
from app.models import GameState
1414

1515
BASE_DIR = Path(__file__).resolve().parent
1616

1717
app = FastAPI(title="Soc Ops - Social Bingo")
18+
app.add_middleware(SessionMiddleware, secret_key="soc-ops-secret-key")
1819
app.mount("/static", StaticFiles(directory=BASE_DIR / "static"), name="static")
1920

2021
templates = Jinja2Templates(directory=BASE_DIR / "templates")
2122

22-
SESSION_COOKIE = "soc_ops_session"
2323

24-
25-
def _session_from_request(
26-
request: Request,
27-
) -> tuple[GameSession, Response]:
28-
"""Get or create session, returning the session and a Response for cookies."""
29-
response = Response()
30-
session_id = request.cookies.get(SESSION_COOKIE)
31-
if not session_id:
32-
session_id = uuid.uuid4().hex
33-
response.set_cookie(SESSION_COOKIE, session_id, httponly=True, samesite="lax")
34-
return get_session(session_id), response
35-
36-
37-
def _render(
38-
request: Request,
39-
response: Response,
40-
template: str,
41-
context: dict[str, Any],
42-
) -> Response:
43-
"""Render a template, copying any set-cookie headers from response."""
44-
content = templates.TemplateResponse(request, template, context)
45-
for key, value in response.headers.items():
46-
if key.lower() == "set-cookie":
47-
content.headers.append(key, value)
48-
return content
24+
def _get_game_session(request: Request):
25+
"""Get or create a game session using Starlette SessionMiddleware."""
26+
if "session_id" not in request.session:
27+
request.session["session_id"] = uuid.uuid4().hex
28+
return get_session(request.session["session_id"])
4929

5030

5131
@app.get("/", response_class=HTMLResponse)
5232
async def home(request: Request) -> Response:
53-
session, response = _session_from_request(request)
54-
return _render(
33+
session = _get_game_session(request)
34+
return templates.TemplateResponse(
5535
request,
56-
response,
5736
"home.html",
5837
{"session": session, "GameState": GameState},
5938
)
6039

6140

6241
@app.post("/start", response_class=HTMLResponse)
6342
async def start_game(request: Request) -> Response:
64-
session, response = _session_from_request(request)
43+
session = _get_game_session(request)
6544
session.start_game()
66-
return _render(
67-
request, response, "components/game_screen.html", {"session": session}
45+
return templates.TemplateResponse(
46+
request, "components/game_screen.html", {"session": session}
6847
)
6948

7049

7150
@app.post("/toggle/{square_id}", response_class=HTMLResponse)
7251
async def toggle_square(request: Request, square_id: int) -> Response:
73-
session, response = _session_from_request(request)
52+
session = _get_game_session(request)
7453
session.handle_square_click(square_id)
75-
return _render(
76-
request, response, "components/game_screen.html", {"session": session}
54+
return templates.TemplateResponse(
55+
request, "components/game_screen.html", {"session": session}
7756
)
7857

7958

8059
@app.post("/reset", response_class=HTMLResponse)
8160
async def reset_game(request: Request) -> Response:
82-
session, response = _session_from_request(request)
61+
session = _get_game_session(request)
8362
session.reset_game()
84-
return _render(
63+
return templates.TemplateResponse(
8564
request,
86-
response,
8765
"components/start_screen.html",
8866
{"session": session, "GameState": GameState},
8967
)
9068

9169

9270
@app.post("/dismiss-modal", response_class=HTMLResponse)
9371
async def dismiss_modal(request: Request) -> Response:
94-
session, response = _session_from_request(request)
72+
session = _get_game_session(request)
9573
session.dismiss_modal()
96-
return _render(
97-
request, response, "components/game_screen.html", {"session": session}
74+
return templates.TemplateResponse(
75+
request, "components/game_screen.html", {"session": session}
9876
)
9977

10078

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ readme = "README.md"
66
requires-python = ">=3.13"
77
dependencies = [
88
"fastapi>=0.115.0",
9+
"itsdangerous>=2.2.0",
910
"jinja2>=3.1.0",
1011
"uvicorn[standard]>=0.34.0",
1112
]

tests/test_api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def test_home_contains_start_screen(self, client: TestClient):
2222

2323
def test_home_sets_session_cookie(self, client: TestClient):
2424
response = client.get("/")
25-
assert "soc_ops_session" in response.cookies
25+
assert "session" in response.cookies
2626

2727

2828
class TestStartGame:

0 commit comments

Comments
 (0)