diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c62262..8278a67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,20 @@ All notable changes to this project will be documented in this file. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) +## [Unreleased] + +### Added +- ET-011: Скачивание GPX из popup публичного трека. Новый эндпоинт + `GET /api/gps-tracks/{track_id}/download` собирает GPX 1.1 из геометрии + трека и отдаёт с `Content-Disposition: attachment` (UTF-8 имя файла по + RFC 5987). В popup на карте появилась кнопка «Скачать GPX» (32×32 CSS px, + mobile-friendly). Реализация: новый модуль `src/api/gps_tracks/export.py` + (`build_gpx`, `safe_filename`); расширение `config/gps_sources.yaml` + per-source флагом `download_allowed` (default-deny; MVP whitelist = `osm`, + см. ADR-015); helper `load_download_allowed_sources` в `config.py`. + Тесты: 13 unit GPX-builder + 10 unit filename + 11 integration download. + ADR-014, ADR-015. Refs: ET-011. + ## [v0.0.2] — 2026-06-02 ### Added diff --git a/config/gps_sources.yaml b/config/gps_sources.yaml index a8fd78c..b26e7af 100644 --- a/config/gps_sources.yaml +++ b/config/gps_sources.yaml @@ -10,6 +10,8 @@ sources: parser_module: "src.api.gps_tracks.sources.osm" save_user_field: true external_url_template: "https://www.openstreetmap.org/user/{user}/traces/{external_id_numeric}" + # ET-011 / ADR-015: ODbL разрешает реэкспорт при атрибуции. + download_allowed: true - id: enduro_russia name: "EnduroRussia.ru" @@ -22,6 +24,8 @@ sources: parser_module: "src.api.gps_tracks.sources.enduro_russia" save_user_field: false source_priority: 80 + # ET-011 / ADR-015: ToS не содержит явного разрешения на ре-экспорт. + download_allowed: false - id: wikiloc name: "Wikiloc" @@ -36,6 +40,8 @@ sources: source_priority: 70 activity_filter: [motorcycle, enduro] max_tracks_per_run: 50 + # ET-011 / ADR-015: proprietary, ToS запрещает массовый ре-экспорт. + download_allowed: false - id: ttrails name: "Тропинки.ру" @@ -47,3 +53,5 @@ sources: attribution: "ttrails.ru" parser_module: "src.api.gps_tracks.sources.ttrails" save_user_field: false + # ET-011 / ADR-015: collection-ADR proposed (blocked), реэкспорт запрещён. + download_allowed: false diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 42ac859..94cde57 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -67,6 +67,13 @@ ADR-007 §6 licensing guard). - z≥12 — GeoJSON через `GET /api/gps-tracks?bbox=...&activity=...&source=...`. - z<8 — слой скрыт (защита от шторма запросов). +Скачивание одного трека из popup карты (ET-011): +`GET /api/gps-tracks/{track_id}/download` — отдаёт GPX 1.1 с +правильным `Content-Disposition` и UTF-8 именем по RFC 5987. Разрешено +только для источников с `download_allowed: true` в +`config/gps_sources.yaml` (MVP: только `osm`). Cap 200000 точек → +413 Payload Too Large. См. ADR-014 / ADR-015. + Health/observability: `GET /api/gps-tracks/health` — состояние БД, число треков по источникам, последний прогон. diff --git a/docs/architecture/adr/README.md b/docs/architecture/adr/README.md index c601608..3033041 100644 --- a/docs/architecture/adr/README.md +++ b/docs/architecture/adr/README.md @@ -17,3 +17,5 @@ | ADR-011 | ttrails.ru — licensing: БЛОКИРОВАН до завершения ToS/robots ревью | proposed | 2026-06-01 | [ET-008](../../work-items/ET-008/06-adr/ADR-011-ttrails-licensing.md) | | ADR-012 | Wikiloc — licensing: accepted с rate-limit 10s, graceful-stop на 403/429, обезличенное сохранение (без user/description) | accepted | 2026-06-01 | [ET-008](../../work-items/ET-008/06-adr/ADR-012-wikiloc-licensing.md) | | ADR-013 | Активация EnduroRussia + Wikiloc — конфиг-only изменения поверх pipeline ET-008 (URL-fix, новая запись wikiloc, регионы, стили, атрибуция) | accepted | 2026-06-01 | [ET-009](../../work-items/ET-009/06-adr/ADR-013-source-activation.md) | +| ADR-014 | GPX-download эндпоинт публичного трека: `xml.etree.ElementTree`-builder + fetch+Blob на клиенте | accepted | 2026-06-03 | [ET-011](../../work-items/ET-011/06-adr/ADR-014-gpx-download-endpoint.md) | +| ADR-015 | Политика реэкспорта публичных треков: per-source `download_allowed` в `gps_sources.yaml`, default-deny (whitelist `osm` для MVP) | accepted | 2026-06-03 | [ET-011](../../work-items/ET-011/06-adr/ADR-015-source-redistribution-policy.md) | diff --git a/docs/work-items/ET-011/00-business-request.md b/docs/work-items/ET-011/00-business-request.md new file mode 100644 index 0000000..eed7111 --- /dev/null +++ b/docs/work-items/ET-011/00-business-request.md @@ -0,0 +1,7 @@ +# Business Request: Скачивание трека из popup на карте (enduro-trails) + +Work Item ID: ET-011 + +## Description + +TBD diff --git a/docs/work-items/ET-011/01-brd.md b/docs/work-items/ET-011/01-brd.md new file mode 100644 index 0000000..b3dbd5c --- /dev/null +++ b/docs/work-items/ET-011/01-brd.md @@ -0,0 +1,123 @@ +# BRD: Скачивание трека из popup на карте + +**Work Item:** ET-011 +**Стадия:** Анализ +**Автор:** analyst +**Дата:** 2026-06-03 + +--- + +## 1. Контекст и проблема + +Пользователь (мотоциклист-эндурист) изучает карту, видит публичные GPS-треки +(слой ET-008 «Публичные треки»), тапает понравившийся трек и видит во +всплывающем окне его метаданные: название, активность, длину, точки, дату, +источники. Однако сейчас **нет способа сохранить трек к себе** — приходится +переходить по внешней ссылке источника (если она есть) и искать там кнопку +скачивания, либо вообще нет возможности (например, в OSM-источнике). + +**Боль:** мотоциклист, готовясь к выезду в офлайн-режиме, не может за один +тап забрать понравившийся трек в свой GPS-навигатор (Garmin, OsmAnd, +Locus, smartphone) или планировщик. + +## 2. Цель + +Дать пользователю **скачать понравившийся трек прямо из popup на карте** +одним нажатием — получить файл в стандартном формате (GPX), пригодный +для импорта в любой GPS-софт. + +## 3. Целевая аудитория + +- Мотоциклист-эндурист, изучающий маршруты перед поездкой +- Велосипедист / турист, скачивающий чужой трек для повторного прохождения +- Турфирма / организатор, готовящая раздаточный материал + +## 4. Бизнес-ценность + +| Метрика | Эффект ожидаемый | +|------------------------------------------------------|-------------------------------------------------| +| Доля сессий с тапом по треку → действие | Сейчас 0% (только просмотр), цель ≥ 20% | +| Возвраты пользователя за треками | ↑ (приложение становится «полезным», а не «смотровым») | +| Конверсия публичных треков в реальные пройденные | ↑ (треки начинают перетекать в GPS) | + +## 5. Область (Scope) + +### В скоупе + +1. **UI:** в существующем popup публичного трека (`_renderTrackPopupHtml` + в `src/web/gps_tracks.js`) появляется кнопка/иконка «Скачать». +2. **Backend:** новый эндпоинт отдачи GPX-файла по идентификатору трека + из таблицы `tracks` БД `gps_tracks.sqlite`. +3. **Формат:** GPX 1.1 — обязательно. +4. **Формат:** KML 2.2 — опционально, если бюджет позволяет (R-K-01, + см. ниже). +5. **Имя файла:** человекочитаемое, из имени трека (см. NFR-04). + +### Вне скоупа + +- Авторизация / приватные треки — все треки в БД публичны. +- Массовое скачивание (пачкой) — только по одному. +- Кастомизация GPX (waypoints, расширения Garmin, цвета) — отдаём + «голую» трассу. +- Скачивание загруженных пользователем GPX (ET-006) — там уже есть + кнопка скачивания в panel `sheet-gpx`, и это другой источник данных. +- Скачивание построенного маршрута (Route / Scenic / Link) — это + отдельный поток `downloadGPX()` в `sheet-route`, не трогаем. +- Регулирование rate limit и квоты — нет, трафик низкий. + +## 6. Пользовательские истории + +**US-1 (Mandatory):** Как мотоциклист, я хочу тапнуть трек на карте, +увидеть popup с его метаданными и нажать «Скачать», чтобы получить GPX-файл +в загрузках браузера — без перехода на сторонний сайт. + +**US-2 (Mandatory):** Как пользователь мобильного браузера, я хочу получить +файл в формате, который мой телефон сразу предложит «Открыть в…» или +«Сохранить» (стандартный `Content-Disposition: attachment`). + +**US-3 (Optional, R-K-01):** Как пользователь Google Earth / некоторых +старых навигаторов, я хочу выбрать формат KML вместо GPX. + +**US-4 (Mandatory):** Как пользователь, я хочу, чтобы имя файла отражало +название трека (а не голый `id.gpx`), чтобы не путаться в загрузках. + +## 7. Ограничения и допущения + +- A1: треки в БД хранятся как WKB LineString в столбце `tracks.geom`, + координаты EPSG:4326 (lon, lat). +- A2: высоты (`ele`) в БД **не хранятся** — отдаём GPX без ``. + Время точек (`time`) — тоже не хранится, отдаём без `