Add offline clip caching

This commit is contained in:
Codex
2026-04-23 13:55:15 -05:00
parent ec2f440c13
commit 51ac5b2060
20 changed files with 554 additions and 27 deletions

View File

@@ -3,11 +3,13 @@ from __future__ import annotations
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi import Request, Response
from sqlalchemy import select
from sqlalchemy.orm import Session
from ..auth import require_session
from ..database import get_db
from ..http_cache import build_etag, is_matching_etag, set_private_revalidate
from ..models import AudioClip, GameAssignment, UserSession
from ..schemas import (
GameAssignmentCreate,
@@ -37,8 +39,20 @@ def assignment_to_response(assignment: GameAssignment) -> GameAssignmentResponse
)
def prepare_conditional_response(
request: Request,
payload: object,
*,
exclude_keys: set[str] | None = None,
) -> tuple[str, bool]:
etag = build_etag(payload, exclude_keys=exclude_keys)
return etag, is_matching_etag(request, etag)
@router.get("/pins", response_model=list[GameAssignmentResponse])
def list_pins(
request: Request,
response: Response,
external_player_id: str | None = Query(default=None),
session: UserSession = Depends(require_session),
db: Session = Depends(get_db),
@@ -52,11 +66,18 @@ def list_pins(
GameAssignment.external_player_id == player_id,
)
pins = db.scalars(query.order_by(GameAssignment.external_game_id.asc(), AudioClip.sort_order.asc())).all()
return [assignment_to_response(assignment) for assignment in pins]
payload = [assignment_to_response(assignment) for assignment in pins]
etag, not_modified = prepare_conditional_response(request, payload)
set_private_revalidate(response, etag=etag)
if not_modified:
return Response(status_code=304, headers=dict(response.headers))
return payload
@router.get("/{external_game_id}/assignments", response_model=list[GameAssignmentResponse])
def list_assignments(
request: Request,
response: Response,
external_game_id: str,
external_player_id: str | None = Query(default=None),
_: UserSession = Depends(require_session),
@@ -69,7 +90,12 @@ def list_assignments(
if external_player_id:
query = query.where(GameAssignment.external_player_id == external_player_id)
assignments = db.scalars(query.order_by(AudioClip.sort_order.asc(), GameAssignment.updated_at.desc())).all()
return [assignment_to_response(assignment) for assignment in assignments]
payload = [assignment_to_response(assignment) for assignment in assignments]
etag, not_modified = prepare_conditional_response(request, payload)
set_private_revalidate(response, etag=etag)
if not_modified:
return Response(status_code=304, headers=dict(response.headers))
return payload
@router.post("/{external_game_id}/assignments", response_model=GameAssignmentResponse)
@@ -136,6 +162,8 @@ def delete_assignment(
@router.get("/{external_game_id}/prep", response_model=GamePrepResponse)
def prepare_game(
request: Request,
response: Response,
external_game_id: str,
_: UserSession = Depends(require_session),
db: Session = Depends(get_db),
@@ -147,9 +175,14 @@ def prepare_game(
.order_by(AudioClip.sort_order.asc(), GameAssignment.updated_at.desc())
).all()
external_team_id = assignments[0].external_team_id if assignments else ""
return GamePrepResponse(
payload = GamePrepResponse(
external_game_id=external_game_id,
external_team_id=external_team_id,
prepared_at=datetime.now(timezone.utc),
assignments=[assignment_to_response(assignment) for assignment in assignments],
)
etag, not_modified = prepare_conditional_response(request, payload, exclude_keys={"prepared_at"})
set_private_revalidate(response, etag=etag)
if not_modified:
return Response(status_code=304, headers=dict(response.headers))
return payload