|
1 | 1 | # src/where_the_plow/main.py |
2 | 2 | import asyncio |
| 3 | +import hashlib |
3 | 4 | import logging |
4 | 5 | from contextlib import asynccontextmanager |
5 | 6 | from pathlib import Path |
6 | 7 |
|
7 | 8 | from fastapi import FastAPI |
8 | | -from fastapi.responses import FileResponse |
| 9 | +from fastapi.responses import FileResponse, HTMLResponse |
9 | 10 | from fastapi.staticfiles import StaticFiles |
10 | 11 |
|
11 | 12 | from where_the_plow import collector |
@@ -56,9 +57,28 @@ async def lifespan(app: FastAPI): |
56 | 57 | app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static") |
57 | 58 |
|
58 | 59 |
|
| 60 | +def _file_hash(path: Path) -> str: |
| 61 | + """Return first 12 chars of the MD5 hex digest of a file's contents.""" |
| 62 | + return hashlib.md5(path.read_bytes()).hexdigest()[:12] |
| 63 | + |
| 64 | + |
| 65 | +def _build_index_html() -> str: |
| 66 | + """Read index.html and append ?v=<hash> to local static asset references.""" |
| 67 | + html = (STATIC_DIR / "index.html").read_text() |
| 68 | + for filename in ("style.css", "app.js"): |
| 69 | + asset_path = STATIC_DIR / filename |
| 70 | + if asset_path.exists(): |
| 71 | + h = _file_hash(asset_path) |
| 72 | + html = html.replace(f"/static/{filename}", f"/static/{filename}?v={h}") |
| 73 | + return html |
| 74 | + |
| 75 | + |
| 76 | +_INDEX_HTML = _build_index_html() |
| 77 | + |
| 78 | + |
59 | 79 | @app.get("/", include_in_schema=False) |
60 | 80 | def root(): |
61 | | - return FileResponse(str(STATIC_DIR / "index.html")) |
| 81 | + return HTMLResponse(_INDEX_HTML) |
62 | 82 |
|
63 | 83 |
|
64 | 84 | @app.get("/health", tags=["system"]) |
|
0 commit comments