18 KiB
type, work_item_id, title, version, status, created_at, authors
| type | work_item_id | title | version | status | created_at | authors | |
|---|---|---|---|---|---|---|---|
| infra-requirements | ET-011 | Инфраструктурные требования — ET-011: Скачивание трека из popup | 1 | approved | 2026-06-03 |
|
Инфраструктурные требования — ET-011
1. Резюме
ET-011 — API-extension only. Добавляется один эндпоинт в
существующий router /api/gps-tracks/* + правки UI-модуля
gps_tracks.js. Инфраструктура не меняется:
- 0 новых docker-сервисов;
- 0 новых файлов БД;
- 0 новых cron-записей;
- 0 новых env / секретов / API-ключей;
- 0 новых исходящих HTTPS-соединений;
- 0 новых портов и nginx-правил.
Все изменения локализованы в:
src/api/gps_tracks/export.py(новый, ~130 строк)src/api/gps_tracks/endpoint.py(+1 route, ~50 строк)src/api/gps_tracks/config.py(+1 optional поле в Pydantic-модели)src/api/main.py(или эквивалент — +1 аргумент при include_router)src/web/gps_tracks.js(+обработчик + правка popup)src/web/app.css(+стиль кнопки)config/gps_sources.yaml(+per-source флагdownload_allowed)- tests (3 новых файла + расширение существующих)
Эскалация: minor change (см. ADR-014 §«Классификация», ADR-015 §«Классификация»).
2. Контейнеры и сервисы
| Аспект | Требование |
|---|---|
| Новый сервис | Нет |
Изменения Dockerfile |
Нет |
Изменения docker-compose.yml |
Нет |
| Перезапуск API после деплоя | Нужен — docker compose up -d --no-deps app (≈ 5 сек простоя). Подхватывает новый route + обновлённые src/web/*.js/*.css/gps_tracks.js |
Перезапуск gps-collector |
Не нужен — pipeline не затронут (collector использует тот же gps_sources.yaml, но игнорирует новое optional-поле download_allowed) |
2.1 Зависимости между сервисами
Без изменений. Новый эндпоинт GET /api/gps-tracks/{id}/download
обслуживается тем же контейнером app, читает ту же БД
/app/data/gps_tracks.sqlite.
3. Сеть
| Аспект | Требование |
|---|---|
| Новые входящие порты | Нет |
| Изменения nginx | Нет (новый route попадает под существующий location /enduro/api/) |
| Новые исходящие соединения с mva154 | Нет |
| CORS | Без изменений; middleware уже отдаёт Access-Control-Allow-Origin: * для всего /api/ |
3.1 Egress trafик
Скачивание GPX — только в downstream браузер. Один типичный трек ≈ 800 КБ (5000 точек) или ≤ 8 МБ (50000 точек). Cap REQ-NF-02: максимум 200000 точек ⇒ ≤ 20 МБ на запрос.
Пиковая оценка: при 20 одновременных скачиваниях типичных треков — ≈ 16 МБ/сек egress; в норме 1–2 одновременно. Не блокирует канал test-сервера (uplink ≥ 100 Mbps по DuckDNS).
3.2 Rate-limit на эндпоинт
Не вводим в этой итерации (BRD §5 «out of scope»). Если в проде
будет аномальный трафик — добавляем slowapi-middleware в отдельном
DevOps-task'е (out of ET-011).
4. Хранилища данных
| Аспект | Требование |
|---|---|
| Новые БД | Нет |
Изменения схемы tracks / pipeline_runs |
Нет |
| Миграции | Нет |
| Новые SELECT-запросы | Один: SELECT … FROM tracks WHERE id = ? (использует PK-индекс, O(log n)) |
| Новые INSERT/UPDATE | Нет (эндпоинт read-only) |
| Backup | Без изменений |
4.1 Производительность БД
Запрос по PK — ~ 1 ms на test-сервере. Сборка GPX через
xml.etree.ElementTree: 5000 точек ≈ 30 ms, 50000 точек ≈ 150 ms,
200000 точек (cap) ≈ 500 ms. Бюджет REQ-NF-01 = 300 ms p95 для
50k точек — соблюдается с запасом.
_wkb_to_coords (переиспользуется из mvt.py) — уже бенчмаркнут в
ET-008: ≈ 1 ms на 1000 точек.
5. Конфигурация и секреты
| Аспект | Требование |
|---|---|
| Новые env-переменные | Нет |
| Новые секреты / API-ключи | Нет |
| Новые конфиг-файлы | Нет; меняется только содержимое config/gps_sources.yaml (+optional поле) |
5.1 Изменения config/gps_sources.yaml
Добавляется одно поле download_allowed: bool per-source. Финальные
значения для ET-011 (см. ADR-015 §«Решение D»):
sources:
- id: osm
# ... existing fields unchanged
download_allowed: true
- id: enduro_russia
# ... existing fields unchanged
download_allowed: false
- id: wikiloc
# ... existing fields unchanged
download_allowed: false
- id: ttrails
# ... existing fields unchanged
download_allowed: false
Все остальные поля (enabled, license_adr, base_url,
rate_limit_sec, user_agent, attribution, parser_module,
source_priority, …) — без изменений.
5.2 Перечитывание конфига
gps_sources.yaml читается при старте контейнера app (один раз) —
в момент create_gps_router(db_path, sources_config_path). Для
изменения политики download_allowed — docker compose up -d --no-deps app
(≈ 5 сек простоя).
6. Зависимости
| Аспект | Требование |
|---|---|
| Новые Python-пакеты (runtime) | Нет (xml.etree.ElementTree, urllib.parse — stdlib Python 3.12) |
| Новые Python-пакеты (dev) | lxml (для XSD-валидации в UT-03 / IT-07). Возможно уже присутствует через defusedxml; добавить в requirements-dev.txt если отсутствует. ~3 МБ |
| Новые JS-зависимости | Нет (vanilla JS + MapLibre API уже доступен) |
| Системные библиотеки в Dockerfile | Нет |
| Версия Python | 3.12, без изменений |
6.1 XSD-фикстура
Файл tests/fixtures/gpx-1.1/gpx.xsd (~30 КБ) — скачивается один
раз разработчиком из http://www.topografix.com/GPX/1/1/gpx.xsd,
коммитится в репо. Не зависит от runtime, не часть production-образа
(на .dockerignore уровне tests/ уже исключён, если нет — проверить).
7. Сборка и деплой
7.1 Pipeline CI
Существующий Gitea Actions:
make lint(ruff + eslint) — должен пройти без замечаний по новому коду (export.py, правкиendpoint.py,gps_tracks.js).make test— должен включать новые тесты:tests/api/test_gps_tracks_gpx_builder.py(UT-01..05)tests/api/test_gps_tracks_filename.py(UT-04 cases)tests/api/test_gps_tracks_download.py(IT-01..08)tests/web/test_track_download.spec.ts(E2E-01..04)
make build— пересобирает образ (никаких изменений в Dockerfile; но новые тестовые фикстуры иgpx.xsdпопадают в репо).
7.2 Деплой шаг-за-шагом
git pull origin mainна mva154.docker compose build(опционально; никаких изменений в Dockerfile/requirements не было).docker compose up -d --no-deps app— рестарт API (≈ 5 сек простоя) для подхвата:- нового эндпоинта
/api/gps-tracks/{id}/download; - обновлённого
src/web/gps_tracks.js(popup + handler); - обновлённого
src/web/app.css(стили кнопки); - расширенного
config/gps_sources.yaml.
- нового эндпоинта
- Smoke в UI:
- Открыть https://openclaw.mva154.duckdns.org/enduro/
- Включить «Публичные треки», тапнуть OSM-трек → видна кнопка
«Скачать» → клик → файл
<name>.gpxв загрузках. - Тапнуть EnduroRussia-трек → клик «Скачать» → toast «Источник запрещает скачивание…» с ссылкой на сайт источника.
- Smoke API:
curl -I https://openclaw.mva154.duckdns.org/enduro/api/gps-tracks/<osm-track-id>/download # ожидаемо: HTTP 200, Content-Type: application/gpx+xml, Content-Disposition: attachment; filename*=UTF-8''… curl -I https://openclaw.mva154.duckdns.org/enduro/api/gps-tracks/99999999/download # ожидаемо: HTTP 404 - Зафиксировать результат в
docs/work-items/ET-011/14-deploy-log.md.
7.3 Время простоя
API: ≤ 5 секунд на шаге 3 (стандартный рестарт контейнера). Pipeline: не задействован.
7.4 Rollback
| Сценарий | Действие | Время |
|---|---|---|
| Откат всего ET-011 | git revert <merge-commit> + docker compose up -d --no-deps app |
≈ 2 мин |
| «Выключить» новый эндпоинт без отката кода | Закомментировать @router.get("/{track_id}/download") или поставить download_allowed: false для всех источников в gps_sources.yaml + рестарт API |
≈ 1 мин |
| Откат БД | Не применимо (схема не менялась) | n/a |
8. Cron / scheduled jobs
Нет новых cron в ET-011. Существующий cron gps-collector (ET-008,
Mon+Thu 03:00 UTC) — без изменений; ET-011 не затрагивает collection.
9. Ресурсы (CPU / RAM / диск)
9.1 API-контейнер
| Метрика | Изменение | Комментарий |
|---|---|---|
| RAM idle | без изменений | загрузка gps_sources.yaml — < 10 КБ |
| RAM на один запрос /download | +5 МБ на 50k точек, +20 МБ на cap 200k | в пиковом сценарии 10 параллельных скачиваний по 200k = +200 МБ; в реальности 1–2 параллельно |
| CPU per запрос | 100–500 мс worker'а | ниже ETC-008 MVT-сборки |
| Disk write | 0 | эндпоинт read-only |
| Disk read | размер записи в tracks (geom ≈ 200 КБ для 50k точек) |
через PK-индекс |
Никаких изменений cpus: / mem_limit: в docker-compose.yml.
9.2 gps-collector контейнер
Не задействован.
9.3 Диск
| Аспект | Изменение |
|---|---|
data/gps_tracks.sqlite |
без изменений (read-only эндпоинт) |
tests/fixtures/gpx-1.1/gpx.xsd |
+30 КБ в репо (не в production-образе) |
| Production-образ docker | без изменений (tests/ исключены) |
10. Наблюдаемость
| Артефакт | Состояние после ET-011 |
|---|---|
uvicorn access-log |
Новые строки 200 GET /api/gps-tracks/<id>/download (через стандартный middleware) |
| Структурный лог (stdout) | Новая строка track_download id=<id> sources=<csv> size_bytes=<n> на каждое 200-скачивание (через logging.getLogger("uvicorn.access").info) |
| 4xx/5xx | Видны в access-log в обычном формате; 5xx — stderr с traceback |
GET /api/gps-tracks/health |
Без изменений (download — read-only, не влияет на counters) |
| Метрики (Prometheus / OpenMetrics) | Не вводим (REQ-NF-06 явно отказывается от метрик в этой итерации) |
10.1 Алерты
Нет новых алертов. При появлении в логах систематических 500 — ручной разбор stack-trace.
10.2 Logrotate
Без изменений (uvicorn пишет в stdout, Docker logger справляется).
11. Безопасность
| Vector | Митигация |
|---|---|
SQL-injection через track_id |
track_id: int = Path(..., ge=1) — FastAPI/Pydantic валидация, далее parameterized SQL |
| Path-traversal в имени файла на диске пользователя | safe_filename() заменяет `/ \ : * ? " < > |
XSS через tracks.name в GPX |
xml.etree.ElementTree экранирует текст и атрибуты автоматически; integration-тест IT-07 валидирует через XSD |
| XML-bomb / external entity в сгенерированном GPX | N/A — мы только генерируем, не парсим. xml.etree.ElementTree (для сборки) не подвержен XXE |
| Утечка PII через скачанный GPX | tracks.user есть только для OSM (ADR-009 разрешает по ODbL); для остальных — null в БД (ADR-010/012); попадает в <author> только если присутствует |
Утечка proprietary metadata через <desc> / <name> |
Для OSM-источника — публичные данные; для не-OSM — <copyright> опускается (ADR-014 §G); если merged через ANY-rule (ADR-015 §B) — компромисс зафиксирован в ADR-015 |
| Утечка лицензионно-защищённой геометрии | License-guard (ADR-015) — 403 для не-разрешённых источников |
| DoS через скачивание трека 50000+ точек | Cap REQ-NF-02 ⇒ 413 для > 200000 точек; rate-limit на API — out of scope |
| Чтение чужой БД через mounted volume | Без изменений (контейнер запускается с user appuser, volume /app/data read-write только для приложения) |
11.1 Лицензионные атаки (юридические риски)
Покрыты ADR-015 (default-deny whitelist). Любой источник без явного
download_allowed: true — недоступен для скачивания. См. 10-tech-risks.md
R-9.
12. Влияние на C4 / архитектурную документацию
12.1 Обновления docs/architecture/README.md
В разделе «GPS Tracks Pipeline (ET-008) → Клиентский слой публичных треков» добавить одну строку после описания GeoJSON-эндпоинта:
- скачивание одного трека через `GET /api/gps-tracks/{track_id}/download`
(GPX 1.1) — разрешено только для источников с
`download_allowed: true` в `config/gps_sources.yaml` (ET-011 / ADR-014 / ADR-015).
12.2 Обновления docs/architecture/adr/README.md
Добавить две строки в таблице индекса ADR:
| # | Решение | Статус | Дата | Источник |
|---|---|---|---|---|
| ADR-014 | GPX-download эндпоинт публичного трека: xml.etree.ElementTree-builder + fetch+Blob на клиенте |
accepted | 2026-06-03 | ET-011 |
| ADR-015 | Политика реэкспорта публичных треков: per-source download_allowed в gps_sources.yaml, default-deny |
accepted | 2026-06-03 | ET-011 |
12.3 C4 mmd-диаграммы
В проекте отсутствуют (см. ET-008 §12, ET-009 §12). ET-011 не вводит новых компонентов или контейнеров — обновление диаграмм не требуется.
13. Вывод
ET-011 — minimal-change на инфра-уровне:
- 0 новых сервисов / 0 новых БД / 0 миграций / 0 новых cron / 0 новых env / 0 новых портов / 0 новых runtime-зависимостей;
- Все изменения локализованы в src-коде, тестах, одной опциональной
ячейке
gps_sources.yaml; - Деплой = git pull + рестарт API;
- Rollback =
git revertили конфиг-флаг.
Эскалация: не требуется (arch:major-change не выставлен; см.
ADR-014, ADR-015).