diff --git a/api/main.py b/api/main.py
index 27981e61..c61a331b 100644
--- a/api/main.py
+++ b/api/main.py
@@ -1,10 +1,12 @@
import asyncio
+import hashlib
import os
from time import perf_counter
from fastapi import Depends, FastAPI, Request
from sqlalchemy.ext.asyncio import AsyncSession
from starlette.middleware.cors import CORSMiddleware
+from starlette.responses import Response as StarletteResponse
from starlette.staticfiles import StaticFiles
from audit import ensure_api_context, log_api_access, record_api_access_event_background
@@ -40,12 +42,29 @@ app.add_middleware(
@app.middleware("http")
-async def security_headers_middleware(request: Request, call_next):
+async def security_and_cache_middleware(request: Request, call_next):
response = await call_next(request)
response.headers.setdefault("X-Content-Type-Options", "nosniff")
response.headers.setdefault("X-Frame-Options", "DENY")
response.headers.setdefault("Referrer-Policy", "strict-origin-when-cross-origin")
response.headers.setdefault("X-XSS-Protection", "1; mode=block")
+
+ content_type = response.headers.get("content-type", "")
+ if request.method == "GET" and response.status_code == 200 and "application/json" in content_type:
+ body = b""
+ async for chunk in response.body_iterator:
+ body += chunk
+ etag = '"' + hashlib.md5(body).hexdigest() + '"'
+ if_none_match = request.headers.get("if-none-match", "")
+ client_etags = [t.strip() for t in if_none_match.split(",") if t.strip()]
+ if etag in client_etags or if_none_match.strip() == "*":
+ return StarletteResponse(status_code=304, headers={"ETag": etag, "Cache-Control": "no-cache"})
+ headers = dict(response.headers)
+ headers["ETag"] = etag
+ headers["Cache-Control"] = "no-cache"
+ return StarletteResponse(content=body, status_code=200, headers=headers, media_type=response.media_type)
+
+ response.headers.setdefault("Cache-Control", "no-store")
return response
diff --git a/handlers/admin/settings/settings_web.py b/handlers/admin/settings/settings_web.py
index 904c7bf0..2834bf70 100644
--- a/handlers/admin/settings/settings_web.py
+++ b/handlers/admin/settings/settings_web.py
@@ -23,7 +23,6 @@ def build_settings_web_kb() -> InlineKeyboardBuilder:
enabled = bool(WEB_CONFIG.get("WEB_ENABLED", False))
url = str(WEB_CONFIG.get("SITE_URL") or "не указан")
-
builder.row(
InlineKeyboardButton(
text=f"{'✅' if enabled else '❌'} Сайт {'включён' if enabled else 'выключен'}",
@@ -47,20 +46,22 @@ def build_settings_web_kb() -> InlineKeyboardBuilder:
return builder
-@router.callback_query(AdminPanelCallback.filter(F.action == "settings_web"))
-async def open_web_settings(callback: CallbackQuery) -> None:
+def _web_settings_text() -> str:
enabled = bool(WEB_CONFIG.get("WEB_ENABLED", False))
url = str(WEB_CONFIG.get("SITE_URL") or "не указан")
-
- text = (
+ return (
"🌐 Настройки веб-сайта\n\n"
f"Статус: {'✅ Включён' if enabled else '❌ Выключен'}\n"
f"URL: {url}\n\n"
"Сайт может работать на отдельном домене и сервере.\n"
"При выключении кнопка «Личный кабинет» скрывается из бота."
)
+
+
+@router.callback_query(AdminPanelCallback.filter(F.action == "settings_web"))
+async def open_web_settings(callback: CallbackQuery) -> None:
await callback.message.edit_text(
- text=text,
+ text=_web_settings_text(),
reply_markup=build_settings_web_kb().as_markup(),
)
await callback.answer()
@@ -77,18 +78,8 @@ async def toggle_web_enabled(callback: CallbackQuery) -> None:
status = "✅ Сайт включён" if new_config["WEB_ENABLED"] else "❌ Сайт выключен"
await callback.answer(status, show_alert=True)
-
- enabled = new_config["WEB_ENABLED"]
- url = str(new_config.get("SITE_URL") or "не указан")
- text = (
- "🌐 Настройки веб-сайта\n\n"
- f"Статус: {'✅ Включён' if enabled else '❌ Выключен'}\n"
- f"URL: {url}\n\n"
- "Сайт может работать на отдельном домене и сервере.\n"
- "При выключении кнопка «Личный кабинет» скрывается из бота."
- )
await callback.message.edit_text(
- text=text,
+ text=_web_settings_text(),
reply_markup=build_settings_web_kb().as_markup(),
)
@@ -193,17 +184,7 @@ async def set_web_url(message: Message, state: FSMContext) -> None:
await update_web_config(session, new_config)
await state.clear()
-
- enabled = new_config.get("WEB_ENABLED", False)
- display_url = url or "не указан"
- text = (
- "🌐 Настройки веб-сайта\n\n"
- f"Статус: {'✅ Включён' if enabled else '❌ Выключен'}\n"
- f"URL: {display_url}\n\n"
- "Сайт может работать на отдельном домене и сервере.\n"
- "При выключении кнопка «Личный кабинет» скрывается из бота."
- )
await message.answer(
- text=text,
+ text=_web_settings_text(),
reply_markup=build_settings_web_kb().as_markup(),
)