mirror of
https://github.com/markbeep/AudioBookRequest.git
synced 2026-01-24 23:20:09 -06:00
Merge pull request #86 from markbeep/add-etag-caching
add etag & cache-control headers to static files
This commit is contained in:
@@ -1,18 +1,20 @@
|
||||
import hashlib
|
||||
from os import PathLike
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
from typing import Annotated, Callable
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from fastapi import APIRouter, Depends, Form, HTTPException, Request, Response
|
||||
from fastapi.responses import FileResponse, RedirectResponse
|
||||
from sqlmodel import Session
|
||||
|
||||
from app.internal.auth.config import LoginTypeEnum, auth_config
|
||||
from app.internal.auth.authentication import (
|
||||
DetailedUser,
|
||||
create_user,
|
||||
get_authenticated_user,
|
||||
raise_for_invalid_password,
|
||||
)
|
||||
from app.internal.auth.config import LoginTypeEnum, auth_config
|
||||
from app.internal.models import GroupEnum
|
||||
from app.util.db import get_session
|
||||
from app.util.templates import templates
|
||||
@@ -22,38 +24,63 @@ router = APIRouter()
|
||||
|
||||
root = Path("static")
|
||||
|
||||
etag_cache: dict[PathLike[str] | str, str] = {}
|
||||
|
||||
|
||||
def add_cache_headers(func: Callable[..., FileResponse]):
|
||||
def wrapper(v: str):
|
||||
file = func()
|
||||
if not (etag := etag_cache.get(file.path)):
|
||||
with open(file.path, "rb") as f:
|
||||
etag = hashlib.sha1(f.read(), usedforsecurity=False).hexdigest()
|
||||
etag_cache[file.path] = etag
|
||||
|
||||
file.headers.append("Etag", etag)
|
||||
# cache for a year. All static files should do cache busting with `?v=<version>`
|
||||
file.headers.append("Cache-Control", f"public, max-age={60 * 60 * 24 * 365}")
|
||||
return file
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@router.get("/static/globals.css")
|
||||
@add_cache_headers
|
||||
def read_globals_css():
|
||||
return FileResponse(root / "globals.css", media_type="text/css")
|
||||
|
||||
|
||||
@router.get("/static/nouislider.css")
|
||||
@add_cache_headers
|
||||
def read_nouislider_css():
|
||||
return FileResponse(root / "nouislider.min.css", media_type="text/css")
|
||||
|
||||
|
||||
@router.get("/static/nouislider.js")
|
||||
@add_cache_headers
|
||||
def read_nouislider_js():
|
||||
return FileResponse(root / "nouislider.min.js", media_type="text/javascript")
|
||||
|
||||
|
||||
@router.get("/static/apple-touch-icon.png")
|
||||
@add_cache_headers
|
||||
def read_apple_touch_icon():
|
||||
return FileResponse(root / "apple-touch-icon.png", media_type="image/png")
|
||||
|
||||
|
||||
@router.get("/static/favicon-32x32.png")
|
||||
@add_cache_headers
|
||||
def read_favicon_32():
|
||||
return FileResponse(root / "favicon-32x32.png", media_type="image/png")
|
||||
|
||||
|
||||
@router.get("/static/favicon-16x16.png")
|
||||
@add_cache_headers
|
||||
def read_favicon_16():
|
||||
return FileResponse(root / "favicon-16x16.png", media_type="image/png")
|
||||
|
||||
|
||||
@router.get("/static/site.webmanifest")
|
||||
@add_cache_headers
|
||||
def read_site_webmanifest():
|
||||
return FileResponse(
|
||||
root / "site.webmanifest", media_type="application/manifest+json"
|
||||
@@ -61,21 +88,37 @@ def read_site_webmanifest():
|
||||
|
||||
|
||||
@router.get("/static/htmx.js")
|
||||
@add_cache_headers
|
||||
def read_htmx():
|
||||
return FileResponse(root / "htmx.js", media_type="application/javascript")
|
||||
return FileResponse(root / "htmx.js", media_type="text/javascript")
|
||||
|
||||
|
||||
@router.get("/static/htmx-preload.js")
|
||||
@add_cache_headers
|
||||
def read_htmx_preload():
|
||||
return FileResponse(root / "htmx-preload.js", media_type="application/javascript")
|
||||
return FileResponse(root / "htmx-preload.js", media_type="text/javascript")
|
||||
|
||||
|
||||
@router.get("/static/alpine.js")
|
||||
@add_cache_headers
|
||||
def read_alpinejs():
|
||||
return FileResponse(root / "alpine.js", media_type="application/javascript")
|
||||
return FileResponse(root / "alpine.js", media_type="text/javascript")
|
||||
|
||||
|
||||
@router.get("/static/toastify.js")
|
||||
@add_cache_headers
|
||||
def read_toastifyjs():
|
||||
return FileResponse(root / "toastify.js", media_type="text/javascript")
|
||||
|
||||
|
||||
@router.get("/static/toastify.css")
|
||||
@add_cache_headers
|
||||
def read_toastifycss():
|
||||
return FileResponse(root / "toastify.css", media_type="text/css")
|
||||
|
||||
|
||||
@router.get("/static/favicon.svg")
|
||||
@add_cache_headers
|
||||
def read_favicon_svg():
|
||||
return FileResponse(root / "favicon.svg", media_type="image/svg+xml")
|
||||
|
||||
|
||||
@@ -98,7 +98,6 @@ def read_users(
|
||||
"page": "users",
|
||||
"users": users,
|
||||
"is_oidc": is_oidc,
|
||||
"version": Settings().app.version,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -230,7 +229,6 @@ def read_prowlarr(
|
||||
"indexer_categories": indexer_categories,
|
||||
"selected_categories": selected,
|
||||
"prowlarr_misconfigured": True if prowlarr_misconfigured else False,
|
||||
"version": Settings().app.version,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -320,7 +318,6 @@ def read_download(
|
||||
"name_ratio": name_ratio,
|
||||
"title_ratio": title_ratio,
|
||||
"indexer_flags": flags,
|
||||
"version": Settings().app.version,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -460,7 +457,6 @@ def read_notifications(
|
||||
"page": "notifications",
|
||||
"notifications": notifications,
|
||||
"event_types": event_types,
|
||||
"version": Settings().app.version,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -605,7 +601,6 @@ def read_security(
|
||||
"oidc_group_claim": oidc_config.get(session, "oidc_group_claim", ""),
|
||||
"oidc_redirect_https": oidc_config.get_redirect_https(session),
|
||||
"oidc_logout_url": oidc_config.get(session, "oidc_logout_url", ""),
|
||||
"version": Settings().app.version,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -733,7 +728,6 @@ async def read_indexers(
|
||||
{
|
||||
"page": "indexers",
|
||||
"indexers": contexts,
|
||||
"version": Settings().app.version,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ files = {
|
||||
"htmx-preload.js": "https://unpkg.com/htmx-ext-preload@2.1.0/preload.js",
|
||||
"htmx.js": "https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js",
|
||||
"alpine.js": "https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js",
|
||||
"toastify.js": "https://cdn.jsdelivr.net/npm/toastify-js",
|
||||
"toastify.css": "https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,12 +6,14 @@ from jinja2_fragments.fastapi import Jinja2Blocks
|
||||
from starlette.background import BackgroundTask
|
||||
|
||||
from app.internal.auth.authentication import DetailedUser
|
||||
from app.internal.env_settings import Settings
|
||||
|
||||
templates = Jinja2Blocks(directory="templates")
|
||||
templates.env.filters["quote_plus"] = lambda u: quote_plus(u) # pyright: ignore[reportUnknownLambdaType,reportUnknownMemberType,reportUnknownArgumentType]
|
||||
templates.env.filters["zfill"] = lambda val, num: str(val).zfill(num) # pyright: ignore[reportUnknownLambdaType,reportUnknownMemberType,reportUnknownArgumentType]
|
||||
templates.env.globals["vars"] = vars # pyright: ignore[reportUnknownMemberType]
|
||||
templates.env.globals["getattr"] = getattr # pyright: ignore[reportUnknownMemberType]
|
||||
templates.env.globals["version"] = Settings().app.version # pyright: ignore[reportUnknownMemberType]
|
||||
|
||||
|
||||
@overload
|
||||
|
||||
10
flake.nix
10
flake.nix
@@ -93,6 +93,14 @@
|
||||
url = "https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js";
|
||||
sha256 = "sha256:1lqa3v5p7pwz3599xnxf5bwxf17bbmqxcqz3cpgj32a8ab9fxl9y";
|
||||
};
|
||||
toastifyjs = builtins.fetchurl {
|
||||
url = "https://cdn.jsdelivr.net/npm/toastify-js";
|
||||
sha256 = "sha256:0v22qkipd2y4z08qkl8hd28d0bgjahn9q08nx05bxfg282zgxavg";
|
||||
};
|
||||
toastifycss = builtins.fetchurl {
|
||||
url = "https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css";
|
||||
sha256 = "sha256:13z5076jlvy1p4fqmmvic3ywbi153jrs0hy8mrl1z45s2js2qgpf";
|
||||
};
|
||||
in
|
||||
|
||||
pkgs.dockerTools.buildImage {
|
||||
@@ -113,6 +121,8 @@
|
||||
cp ${htmx-preload} $out/app/static/htmx-preload.js
|
||||
cp ${htmx} $out/app/static/htmx.js
|
||||
cp ${alpinejs} $out/app/static/alpine.js
|
||||
cp ${toastifyjs} $out/app/static/toastify.js
|
||||
cp ${toastifycss} $out/app/static/toastify.css
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
2
static/.gitignore
vendored
2
static/.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
alpine.js
|
||||
htmx.js
|
||||
htmx-preload.js
|
||||
toastify.js
|
||||
toastify.css
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
{% block head %}
|
||||
<title>AudioBookRequest</title>
|
||||
{% endblock %}
|
||||
<link rel="stylesheet" href="/static/globals.css" />
|
||||
<script src="/static/htmx.js"></script>
|
||||
<script defer src="/static/htmx-preload.js"></script>
|
||||
<link rel="stylesheet" href="/static/globals.css?v={{ version }}" />
|
||||
<script src="/static/htmx.js?v={{ version }}"></script>
|
||||
<script defer src="/static/htmx-preload.js?v={{ version }}"></script>
|
||||
<script>
|
||||
const setTheme = theme => {
|
||||
if (!theme) {
|
||||
@@ -42,27 +42,27 @@
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/static/apple-touch-icon.png"
|
||||
href="/static/apple-touch-icon.png?v={{ version }}"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
sizes="any"
|
||||
type="image/svg+xml"
|
||||
href="/static/favicon.svg"
|
||||
href="/static/favicon.svg?v={{ version }}"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/static/favicon-32x32.png"
|
||||
href="/static/favicon-32x32.png?v={{ version }}"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/static/favicon-16x16.png"
|
||||
href="/static/favicon-16x16.png?v={{ version }}"
|
||||
/>
|
||||
<link rel="manifest" href="/static/site.webmanifest" />
|
||||
<link rel="manifest" href="/static/site.webmanifest?v={{ version }}" />
|
||||
|
||||
{% include 'scripts/toast.html' %}
|
||||
</head>
|
||||
|
||||
@@ -1 +1 @@
|
||||
<script defer src="/static/alpine.js"></script>
|
||||
<script defer src="/static/alpine.js?v={{ version }}"></script>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css"
|
||||
href="/static/toastify.css?v={{ version }}"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.jsdelivr.net/npm/toastify-js"
|
||||
src="/static/toastify.js?v={{ version }}"
|
||||
></script>
|
||||
<script>
|
||||
const toast = (message, type = "success") => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "settings_page/base.html" %} {% block head %}
|
||||
<title>Settings - Download</title>
|
||||
<link href="/static/nouislider.css" rel="stylesheet" />
|
||||
<script src="/static/nouislider.js"></script>
|
||||
<link href="/static/nouislider.css?v={{ version }}" rel="stylesheet" />
|
||||
<script src="/static/nouislider.js?v={{ version }}"></script>
|
||||
<script>
|
||||
const createSlider = (sliderId, fromId, toId, start, stop) => {
|
||||
const slider = document.getElementById(sliderId);
|
||||
|
||||
Reference in New Issue
Block a user