Files
NHL-Scoreboard/app/__init__.py
T
josh 7d1649d278
CI / Lint (push) Successful in 14s
CI / Test (push) Successful in 12s
CI / Build & Push (push) Successful in 32s
feat: cache-control overhaul so visual changes propagate immediately
Per-file content-hash versioning on every /static reference, immutable cache
headers on versioned URLs, no-cache on HTML, auto-bumped service worker cache
name with stale-while-revalidate for assets, and a controllerchange listener
that silently reloads the page when a new SW takes control.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 20:11:36 -04:00

84 lines
2.4 KiB
Python

import hashlib
import logging
import time
from pathlib import Path
from flask import Flask, request
from app.config import LOG_LEVEL
logging.basicConfig(
level=getattr(logging, LOG_LEVEL.upper(), logging.INFO),
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
app = Flask(__name__)
# ── Static asset versioning ────────────────────────────────────────
# Each /static/<file> reference gets a ?v=<hash> query string so we can serve
# it with `Cache-Control: immutable` and still bust the cache when bytes change.
_FALLBACK_TOKEN = hashlib.sha1(str(time.time()).encode()).hexdigest()[:8]
_static_hashes: dict[str, str] = {}
def _hash_file(path: Path) -> str:
h = hashlib.sha1()
with path.open("rb") as fh:
for chunk in iter(lambda: fh.read(65536), b""):
h.update(chunk)
return h.hexdigest()[:8]
def static_hash(filename: str) -> str:
if filename in _static_hashes:
return _static_hashes[filename]
path = Path(app.static_folder) / filename
try:
token = _hash_file(path)
except OSError:
logging.getLogger(__name__).warning(
"static_hash: cannot hash %s, using fallback token", filename
)
return _FALLBACK_TOKEN
_static_hashes[filename] = token
return token
def static_v(filename: str) -> str:
return f"/static/{filename}?v={static_hash(filename)}"
_static_dir = Path(app.static_folder)
if _static_dir.is_dir():
for _path in sorted(_static_dir.iterdir()):
if _path.is_file():
try:
_static_hashes[_path.name] = _hash_file(_path)
except OSError:
continue
APP_VERSION = (
hashlib.sha1(
"|".join(f"{n}:{h}" for n, h in sorted(_static_hashes.items())).encode()
).hexdigest()[:8]
if _static_hashes
else _FALLBACK_TOKEN
)
app.jinja_env.globals["static_v"] = static_v
@app.after_request
def _add_cache_headers(response):
if response.mimetype == "text/html":
response.headers["Cache-Control"] = "no-cache, must-revalidate"
return response
if request.path.startswith("/static/") and request.args.get("v"):
response.headers["Cache-Control"] = "public, max-age=31536000, immutable"
return response
from app import routes # noqa: E402, F401