perf: cache public catalog responses
This commit is contained in:
@@ -35,6 +35,15 @@ app.add_middleware(
|
|||||||
allow_headers=["Authorization", "Content-Type"],
|
allow_headers=["Authorization", "Content-Type"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PUBLIC_CATALOG_CACHE_CONTROL = "public, max-age=60, stale-while-revalidate=60"
|
||||||
|
|
||||||
|
|
||||||
|
def _is_public_catalog_path(path: str) -> bool:
|
||||||
|
return path in {
|
||||||
|
"/api/v1/fishes", "/api/v1/waterbodies", "/api/v1/baits",
|
||||||
|
"/api/v1/tackle/items", "/api/v1/tackle/rigs",
|
||||||
|
} or path.startswith(("/api/v1/tackle/items/", "/api/v1/tackle/rigs/"))
|
||||||
|
|
||||||
|
|
||||||
@app.middleware("http")
|
@app.middleware("http")
|
||||||
async def structured_request_log(request: Request, call_next):
|
async def structured_request_log(request: Request, call_next):
|
||||||
@@ -52,6 +61,8 @@ async def structured_request_log(request: Request, call_next):
|
|||||||
response.headers["Cross-Origin-Opener-Policy"] = "same-origin"
|
response.headers["Cross-Origin-Opener-Policy"] = "same-origin"
|
||||||
if request.url.path.startswith("/api/v1/admin/") or request.url.path == "/api/v1/catch-reports":
|
if request.url.path.startswith("/api/v1/admin/") or request.url.path == "/api/v1/catch-reports":
|
||||||
response.headers["Cache-Control"] = "no-store"
|
response.headers["Cache-Control"] = "no-store"
|
||||||
|
elif request.method == "GET" and response.status_code == 200 and _is_public_catalog_path(request.url.path):
|
||||||
|
response.headers["Cache-Control"] = PUBLIC_CATALOG_CACHE_CONTROL
|
||||||
if settings.deployment_environment == "production":
|
if settings.deployment_environment == "production":
|
||||||
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
|
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ def test_explicit_source_conflict_is_exposed_on_activity_and_spot() -> None:
|
|||||||
def test_waterbody_catalog_exposes_nullable_source_provenance() -> None:
|
def test_waterbody_catalog_exposes_nullable_source_provenance() -> None:
|
||||||
response = client.get("/api/v1/waterbodies")
|
response = client.get("/api/v1/waterbodies")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
assert response.headers["cache-control"] == "public, max-age=60, stale-while-revalidate=60"
|
||||||
item = next(row for row in response.json() if row["slug"] == "test-lake")
|
item = next(row for row in response.json() if row["slug"] == "test-lake")
|
||||||
assert item["source_system"] is None
|
assert item["source_system"] is None
|
||||||
assert item["source_external_id"] is None
|
assert item["source_external_id"] is None
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ R-пункты уточняют критерии существующих B/G/U/
|
|||||||
- [x] **R11 · P2 · Честное качество и происхождение media (B25).** API использует точный hostname allowlist для `rf4db`/`rf4map`/`rf4-stat`, неизвестные и malformed URL получают `unknown`, а не ложный `rf4-official`. UI различает низкое разрешение и наличие проверенной card-версии, не утверждая отсутствие альтернатив без данных manifest. Cache/revocation и production visual acceptance остаются отдельными gates.
|
- [x] **R11 · P2 · Честное качество и происхождение media (B25).** API использует точный hostname allowlist для `rf4db`/`rf4map`/`rf4-stat`, неизвестные и malformed URL получают `unknown`, а не ложный `rf4-official`. UI различает низкое разрешение и наличие проверенной card-версии, не утверждая отсутствие альтернатив без данных manifest. Cache/revocation и production visual acceptance остаются отдельными gates.
|
||||||
- [x] **R14 · P2 · Подтверждённый каталог и demo seed.** `SEED_DEMO_DATA=false` больше не создаёт редакционные описания тестовых точек; demo-уловы имеют отдельный `seed:` namespace и catalog audit считает их отдельно от подтверждённых reports. Loaded canonical counts и external staging остаются раздельными, media count не используется как catalog count.
|
- [x] **R14 · P2 · Подтверждённый каталог и demo seed.** `SEED_DEMO_DATA=false` больше не создаёт редакционные описания тестовых точек; demo-уловы имеют отдельный `seed:` namespace и catalog audit считает их отдельно от подтверждённых reports. Loaded canonical counts и external staging остаются раздельными, media count не используется как catalog count.
|
||||||
- [x] **R15 · P2 · Восстановление пользовательских действий.** Report UI теперь различает idempotency-конфликт и обычную ошибку, восстанавливает draft и предлагает повтор с новым ключом; plan получил восьмисекундный undo после удаления с обработкой storage errors. Draft сохраняется при 422/429/timeout, повтор не требует заново заполнять весь улов.
|
- [x] **R15 · P2 · Восстановление пользовательских действий.** Report UI теперь различает idempotency-конфликт и обычную ошибку, восстанавливает draft и предлагает повтор с новым ключом; plan получил восьмисекундный undo после удаления с обработкой storage errors. Draft сохраняется при 422/429/timeout, повтор не требует заново заполнять весь улов.
|
||||||
- [ ] **R16 · P2 · Производительность реальных endpoints.** Первый шаг выполнен: public media catalog использует process-local digest/mtime cache manifest с инвалидированием при изменении файла, API отдаёт короткий `Cache-Control` и manifest version. Теперь `published_file` использует revision-local digest index без повторного сканирования assets, а activity/tackle analytics ограничены воспроизводимым окном 10 000 свежих фактов. Осталось измерить ORM/RSS/SQL на крупных fixtures и зафиксировать endpoint budget; Redis заранее не добавлять.
|
- [ ] **R16 · P2 · Производительность реальных endpoints.** Первый шаг выполнен: public media catalog использует process-local digest/mtime cache manifest с инвалидированием при изменении файла, API отдаёт короткий `Cache-Control` и manifest version. Теперь `published_file` использует revision-local digest index без повторного сканирования assets, activity/tackle analytics ограничены воспроизводимым окном 10 000 свежих фактов, а справочники и detail-карточки снастей получают безопасный `public` cache policy на 60 секунд. Осталось измерить ORM/RSS/SQL на крупных fixtures и зафиксировать endpoint budget; Redis заранее не добавлять.
|
||||||
- [x] **R17 · P2 · Навигация и SEO снастей.** Sitemap теперь включает `/tackle`, все пагинированные canonical item/rig detail URL и не добавляет фильтры/analytics; detail layout оставляет missing/unavailable/404 страницы noindex, а каталог ведёт в canonical карточки. При недоступном API сохраняется последний корректный sitemap.
|
- [x] **R17 · P2 · Навигация и SEO снастей.** Sitemap теперь включает `/tackle`, все пагинированные canonical item/rig detail URL и не добавляет фильтры/analytics; detail layout оставляет missing/unavailable/404 страницы noindex, а каталог ведёт в canonical карточки. При недоступном API сохраняется последний корректный sitemap.
|
||||||
- [ ] **R12 · P2 · Визуальная приёмка наполненных состояний (D07/A06/U06/G09).** Изолированные данные: длинные карточки, компоненты, конфликты, ошибки, authenticated admin; light/dark mobile/desktop, keyboard/contrast и screenshots с revision. Критерий: подтверждены действия и читаемость, а не только DOM/overflow.
|
- [ ] **R12 · P2 · Визуальная приёмка наполненных состояний (D07/A06/U06/G09).** Изолированные данные: длинные карточки, компоненты, конфликты, ошибки, authenticated admin; light/dark mobile/desktop, keyboard/contrast и screenshots с revision. Критерий: подтверждены действия и читаемость, а не только DOM/overflow.
|
||||||
- [ ] **R13 · P2 · Зелёные проверки и CI artifacts.** Обновить два устаревших контракта, адресные regressions исправленных дефектов, успешные visual artifacts, Node/Python baseline и отдельный dependency audit. Критерий: suites зелёные, skip объяснён, отчёт связан с commit. Известные падения учитывать при каждом feature-коммите.
|
- [ ] **R13 · P2 · Зелёные проверки и CI artifacts.** Обновить два устаревших контракта, адресные regressions исправленных дефектов, успешные visual artifacts, Node/Python baseline и отдельный dependency audit. Критерий: suites зелёные, skip объяснён, отчёт связан с commit. Известные падения учитывать при каждом feature-коммите.
|
||||||
|
|||||||
Reference in New Issue
Block a user