feat: Update API versioning and parser handling for improved schedule updates and error management

This commit is contained in:
2026-10-19 23:29:35 +03:00
parent 85c5baa3e5
commit 68f9b95090
10 changed files with 32 additions and 18 deletions

View File

@@ -47,13 +47,13 @@ Vite работает на http://127.0.0.1:5173 и проксирует `/api`
**Семестры.** Таблица делится на семестры там, где нумерация недель начинается заново или между неделями больше трёх недель. Расписание связывается со своим семестром по тексту Word («осенний семестр 2026/2027»), расписание сессии — по своим датам, документ без указания семестра — по семестру, в котором сегодняшняя дата. Если семестр не найден (например, весенней таблицы ещё нет), даты не рассчитываются и показываются все строки Word.
**Обновления у студента.** При запуске и при возвращении в приложение, если прошло больше часа, клиент сверяет сохранённое расписание с каталогом. Тот же файл с новой датой тихо перечитывается. Если для группы появился более новый документ (следующий семестр или год), показывается предложение его открыть; отказ запоминается. Если документ исчез из каталога, приложение предлагает найти группу заново.
**Обновления у студента.** При запуске и при возвращении в приложение, если прошло больше часа, клиент сверяет сохранённое расписание с каталогом. Тот же файл с новой датой тихо перечитывается. Так же один раз перечитывается расписание, сохранённое более старым парсером, чем на сервере: `/api/catalog` сообщает `parserVersion`, и PWA сравнивает его с версией сохранённой копии. Поэтому исправления разбора доходят до студентов без перепубликации документа. У сервера без `parserVersion` перечитывания нет. Если для группы появился более новый документ (следующий семестр или год), показывается предложение его открыть; отказ запоминается. Если документ исчез из каталога, приложение предлагает найти группу заново.
Каталог и папка года перечитываются раз в 15 минут, таблица недель и звонки — раз в 6 часов, распознанный документ — при изменении даты файла или версии парсера. Устаревшая копия отвечает сразу, а перечитывание идёт в фоне. Ждать приходится только при первом запуске, в начале нового учебного года и при «Обновить». «Обновить» (`?refresh=1`) принудительно перечитывает всё, но обращается к университету не чаще раза в 2 минуты (таблицу недель — раз в 10 минут), иначе отвечает только что прочитанной копией. После сбоя источника фоновые попытки тоже ждут 2 минуты. Если источник недоступен, используются сохранённые копии с явной пометкой. Фоновая синхронизация при закрытом приложении не реализована.
Ответы API сжимаются (gzip или brotli; семестр группы — около 200 КБ JSON, в gzip — около 5 КБ) и отдаются с `Cache-Control: no-cache` и `ETag`. Клиент хранит ответ, но каждый раз спрашивает заново. Если ничего не изменилось, приходит пустой `304`. Android-клиент делает это сам через дисковый кэш OkHttp. Импорт Word ограничен: два разбора одновременно, ещё четыре ждут, остальным — `429`; с одного адреса — до 10 файлов за 10 минут. За прокси (Caddy, туннель) адрес клиента берётся из `X-Forwarded-For`, если прокси находится в локальной или частной сети. Для `/api/timetable` ETag считается до любой работы — из версии документа, парсера и сервера, таблицы недель, параметров запроса и даты, — поэтому неизменённое расписание отвечает `304` примерно за миллисекунду, не открывая документ.
Ошибки приходят как `{ "error": "текст для показа", "code": "…" }`. Клиенты опираются на `code`, а не на текст: `bad_request` (400), `unknown_endpoint`, `document_not_found`, `group_not_found` (404), `file_too_large` (413), `unreadable_file` (422), `too_many_requests` и `busy` (429), `source_unavailable` и `source_invalid` (502, каталог университета не ответил или не распознан), `source_timeout` (504), `internal` (500). Полный список — в `server/errors.js`. Android и iOS при `source_unavailable`, `source_timeout` и `busy` оставляют на экране сохранённую копию и показывают «Повторить».
Ошибки приходят как `{ "error": "текст для показа", "code": "…" }`. Клиенты опираются на `code`, а не на текст: `bad_request` (400), `unknown_endpoint`, `document_not_found`, `group_not_found` (404), `file_too_large` (413), `unreadable_file` (422), `too_many_requests` и `busy` (429), `source_unavailable` и `source_invalid` (502, каталог университета не ответил или не распознан), `source_timeout` и `timeout` (504, университет или сам сервер не ответили вовремя), `internal` (500). Полный список — в `server/errors.js`. Android и iOS при `source_unavailable`, `source_timeout`, `timeout`, `busy` и `too_many_requests` оставляют на экране сохранённую копию и показывают «Повторить».
К каталогу университета сервер обращается встроенным в Node `fetch`: соединения переиспользуются, сертификаты проверяются, перенаправления не принимаются, ответ ограничен 10 МБ и 25 секундами. Обход каталога (около сотни папок) занимает несколько секунд.