Видно, изменился ли файл при обновлении (sha256 + отметка в админке)

Просьба с эксплуатации: после «Обновить сейчас» непонятно, подхватились ли
правки таблицы. Теперь кэш считает sha256 скачанного файла и сравнивает с
предыдущим чтением:

- в логе: «Расписание загружено: N групп …; файл изменился/не изменился
  (sha256 ab12cd34)»;
- состояние для админки дополнено полями changed / changed_at / content_hash;
- в панели чипы «Файл при последнем обновлении», «Содержимое менялось»,
  «sha256 файла», а после кнопки — понятное сообщение: «обнаружены изменения»
  либо «файл не изменился (на Диске та же версия)» с подсказкой про задержку
  публикации у Яндекса;
- ответы API отдаются с Cache-Control: no-store, чтобы браузер не показывал
  закэшированные данные после обновления;
- запросы к Яндекс.Диску идут с no-cache заголовками и меткой времени —
  меньше шансов получить закэшированную версию файла.

Тест: test_state_reports_file_changes (первая загрузка, изменение файла,
повторное чтение без изменений).
This commit is contained in:
2026-09-29 15:27:01 +03:00
parent d4b47c30bf
commit d177560f7d
6 changed files with 96 additions and 8 deletions

View File

@@ -15,7 +15,7 @@ import uvicorn
from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException, Request, Response
from fastapi.concurrency import run_in_threadpool
from fastapi.responses import FileResponse
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
log = logging.getLogger(__name__)
@@ -51,7 +51,8 @@ def _proxy(path: str, params: dict) -> object:
except Exception:
detail = resp.text
raise HTTPException(status_code=resp.status_code, detail=detail)
return resp.json()
# данные не кэшируем: после «Обновить сейчас» браузер должен видеть свежее
return JSONResponse(content=resp.json(), headers={"Cache-Control": "no-store"})
def _proxy_admin(method: str, path: str, body: bytes, headers: dict) -> Response:
@@ -70,6 +71,7 @@ def _proxy_admin(method: str, path: str, body: bytes, headers: dict) -> Response
content=resp.content,
status_code=resp.status_code,
media_type=resp.headers.get("content-type"),
headers={"Cache-Control": "no-store"},
)
# Set-Cookie (вход/выход админки) передаём как есть
raw_headers = getattr(resp.raw, "headers", None)