From 3b913956e91ab67235dde5f792c561c514c6d0ea Mon Sep 17 00:00:00 2001 From: Vladless Date: Fri, 17 Apr 2026 22:59:01 +0000 Subject: [PATCH] Disable all caching layers for instant content updates --- api/main.py | 21 +++++++++++++- handlers/admin/settings/settings_web.py | 37 ++++++------------------- 2 files changed, 29 insertions(+), 29 deletions(-) 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(), )