Админка: секретный путь, смена ссылки, интервал и принудительное обновление

Панель без публичного входа: страница живёт по ADMIN_PATH, логин/пароль —
ADMIN_LOGIN/ADMIN_PASSWORD из .env (пусто = админка выключена и маршруты
/api/admin/* не регистрируются, прод можно не трогать).

Умеет: посмотреть текущую ссылку и состояние кэша, вставить новую ссылку
(проверяется скачиванием и разбором до сохранения, при ошибке откат),
принудительно обновить таблицу вне интервала и без проверки modified,
задать интервал в минутах (1..1440).

Реализация:
- backend/admin.py — cookie-сессия на HMAC (ключ = пароль, 12 часов,
  HttpOnly/SameSite/Secure-за-https), сравнение за постоянное время;
  compare_digest по байтам — кириллица в логине не роняет сервер;
- schedule/settings.py — ссылка и интервал в JSON (SETTINGS_FILE), атомарная
  запись, терпимость к битому/недоступному файлу (тогда живёт в памяти);
- schedule/cache.py — интервал вместо TTL, apply_link с откатом,
  force_reload, state() для панели; кэш без ссылки больше не роняет старт;
- frontend: страница admin.html (вне static, прямой /admin.html закрыт) и
  прокси /api/admin/* с телом и Set-Cookie;
- docker-compose: том schedule_data для настроек, ADMIN_* в окружении;
- deploy/: nginx-конфиг и инструкция для стенда dev.schedule.our-place.ru.

Тесты: 138 (новые — админ-API, сессии, ссылка/интервал, force, хранилище).
This commit is contained in:
2026-09-28 12:06:25 +03:00
parent 0e62d5ca8d
commit cfa6a99639
16 changed files with 1285 additions and 83 deletions

View File

@@ -85,7 +85,7 @@ def make_sheets(sheets: dict[str, SheetSpec]):
return parse_workbook(build_workbook(sheets))
def make_cache(monkeypatch, xlsx: bytes | None, modified: str = "fixed-modified"):
def make_cache(monkeypatch, xlsx: bytes | None, modified: str = "fixed-modified", settings=None):
"""ScheduleCache с заменёнными сетевыми функциями."""
import schedule.cache as cache_mod
from schedule import ScheduleCache
@@ -107,5 +107,5 @@ def make_cache(monkeypatch, xlsx: bytes | None, modified: str = "fixed-modified"
monkeypatch.setattr(cache_mod, "download_schedule", fake_download)
monkeypatch.setattr(cache_mod, "fetch_table_modified", fake_modified)
cache = ScheduleCache("test-link", ttl_seconds=3600, retry_after=60)
cache = ScheduleCache("test-link", ttl_seconds=3600, retry_after=60, settings=settings)
return cache, calls, fail