Commit Graph

13 Commits

Author SHA1 Message Date
f69003be75 Фикс сборки: api-образ не копировал admin.py (контейнер падал на импорте)
Dockerfile для stage api копировал только main.py и schedule/, поэтому в
контейнере 'from admin import ...' давал ModuleNotFoundError — api стартовал
и сразу падал, compose останавливался на 'dependency failed to start'.

Добавлен COPY admin.py и тест tests/test_deploy.py: проверяет, что в образ
api попадают все модули backend, в frontend — страница админки и статика,
а compose передаёт ADMIN_* и подключает том настроек.
2026-09-28 13:37:23 +03:00
e007a85f56 Админка: защита от перебора пароля и правка дефолта SETTINGS_FILE
- не больше 10 неудачных входов с одного адреса за 5 минут, дальше 429
  (панель доступна из интернета, простой перебор закрываем);
  успешный вход сбрасывает счётчик;
- .env.example: SETTINGS_FILE пустой — локально настройки живут в памяти,
  в docker compose подставляет /data/settings.json по умолчанию.
2026-09-28 12:07:45 +03:00
cfa6a99639 Админка: секретный путь, смена ссылки, интервал и принудительное обновление
Панель без публичного входа: страница живёт по 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, хранилище).
2026-09-28 12:06:25 +03:00
0e62d5ca8d Фронт: точки переноса в длинных ссылках (<wbr>), не полагаемся на CSS
Ссылка на онлайн-курс в поле аудитории — одно длинное слово. CSS-фикс
(overflow-wrap/word-break) работает в современных движках, но на телефоне
пользователя переполнение оставалось: старый движок или закэшированный CSS.

Теперь фронтенд сам расставляет <wbr>: после служебных символов ссылок
(/, ?, &, =, ., _, -) и не реже, чем каждые 24 символа; экранирование
сохраняется. Перенос работает в любом браузере.

Проверено в headless Chromium (390px и 320px): переполнений нет как с
включёнными CSS-переносами, так и с принудительно отключёнными
(эмуляция старого движка).
2026-09-22 15:32:43 +03:00
4685301773 Фронт: длинные ссылки переносятся, а не растягивают карточку
На телефонах ссылка вида https://next.uniyar.ac.ru/course/view.php?id=407
в поле аудитории растягивала карточку занятия и страницу по горизонтали.

- .period и .room-row: grid-колонка контента minmax(0, 1fr), иначе
  трек не может сжаться ниже min-content длинного слова
- .lesson и .room-card: min-width: 0 + word-break: break-word +
  overflow-wrap: anywhere (перенос по буквам, работает и в старых iOS)
- у main min-width: 0 уже был, так что переполнение ушло
2026-09-21 23:03:51 +03:00
3247ebcd64 Фронт: без бейджа «Обе недели», недели на страницах преподов/аудиторий, иконка приложения
- пары с одинаковыми ч/з показываются одним списком без пометки недели
  (группы, преподаватели, аудитории); раньше на страницах преподавателей
  и аудиторий такое занятие дублировалось в обеих колонках
- бейдж «Обе недели» убран как информационный шум (отсутствие пометки
  и означает «любая неделя»)
- favicon: квадрат с градиентом боковой панели (#1b2dd8 → #167cf2) и совой
- PWA: manifest.json, sw.js, apple-touch-icon и иконки 192/512 —
  при добавлении на главный экран смартфона появляется логотип
- tools/make_icons.py — генерация иконок из логотипа (rsvg-convert)
- удалён неиспользуемый roomPeriodCell и мёртвый favicon.png
2026-09-21 21:56:34 +03:00
419e65e960 Парсер: замечания отдельной строкой — уровня всей ячейки
В ячейке с несколькими занятиями (курсы по выбору) красная заметка,
занимающая строку(и) целиком, относится ко всем занятиям ячейки
(пара отменяется целиком) и раздаётся каждому занятию. Заметка,
написанная в одной строке с предметом/аудиторией, остаётся
привязанной к своему занятию. Многострочные standalone-заметки
сохраняют переводы строк.

Кейс: ПИЭ-13МО, понедельник, 5-я пара. Diff по 33 группам: затронуто
только поле note в multi-lesson ячейках; порядок «inline->ячейка».
2026-09-14 01:37:53 +03:00
e61ff1d683 xlsx: чистить «осиротевшие» значения в мержах (призрачные занятия)
В интерфейсе в объединённом блоке видно только верхне-левую ячейку;
значения, оставшиеся в остальных ячейках мержа (занесли текст, а сверху
наложили пустой мерж), в таблице не видны. Раньше парсер их отдавал:
реальный кейс — ПИЭ-41БО, вторник, 1 пара, знаменатель (мерж AH14:AI15).
По листу таких ячеек ровно одна; по 33 группам diff только этот слот.
2026-09-09 18:00:13 +03:00
5ac3db50d3 README: запуск (prod/dev), архитектура, устройство парсера, API, деплой 2026-09-09 15:11:46 +03:00
9c9875579c Тесты и инфраструктура
- tests/: 104 теста — lessons (все форматы ячеек), xlsx (мержи/скрытые
  столбцы/красный текст/даты), parsing (группы/недели/индексы), API (TestClient,
  TTL/modified/stale-while-error, 404/400/502)
- xlsx_builder.py: генерация xlsx в памяти для тестов без openpyxl
- docker-compose: healthz-хелскчеки (не блокируют старт), SCHEDULE_LINK
- Makefile: target test
- requirements: закреплённые версии
- .gitignore: __pycache__/, .venv/ и т.д.
2026-09-09 11:33:58 +03:00
2febf9b4ff Фронтенд: стабильность прокси и рендера
- main.py: таймаут прокси 150с (холодная загрузка бэкенда до ~2мин), /healthz, чистый список маршрутов
- app.js: таймаут fetch (AbortController), защита от гонок при переключении вкладок (requestSeq)
- style.css: стили chипа ошибки кэша (multi-line заметки уже поддерживались)
2026-09-09 11:33:43 +03:00
86c2a3097d Бэкенд: пакет schedule/ по зонам ответственности, обобщённый разбор ячеек
- xlsx.py: чтение книги (мержи, скрытые столбцы, красный текст), фикс дат до 1900
- lessons.py: общая схема ролей (предмет/преподаватель/аудитория/заметка)
  вместо ветвлений по числу строк; маркеры к.в., многострочные заметки,
  ФИО с/без отчества, организации, локации, метки групп -> заметки
- source.py: загрузка с Яндекс.Диска с понятными ошибками
- parsing.py: листы -> расписание, недели семестра, индексы преподов/аудиторий
- cache.py: lock, TTL, проверка modified, stale-while-error с retry cooldown
- main.py: create_app, /healthz, логирование; force-обновление убрано (вернётся с авторизацией)
- переменная окружения SCHEDULE_LINK
2026-09-09 11:33:19 +03:00
dcd9b854a0 Базовая функциональность 2026-09-09 08:25:53 +03:00