# RideBus DE API Система управления информацией об общественном транспорте города Полоцка с REST API, веб-панелью администратора и поддержкой offline-синхронизации. ## 🚀 Возможности ### MVP (Версия 1.0 - статические данные) - ✅ REST API для маршрутов и остановок - ✅ Геопространственные запросы (PostGIS) - ✅ Расчет ETA на основе расписания - ✅ Offline-синхронизация для мобильных приложений - ✅ Веб-панель администратора - ✅ Кеширование через Redis - ✅ Аутентификация по API ключам - ✅ Rate limiting - ✅ Серверные push-уведомления об алертах через Firebase Cloud Messaging - ✅ Импорт отмен из Telegram через отдельный аккаунт, проверка в админке и срок действия до полуночи ([настройка](docs/telegram-cancellation-import.md)) ### Технологический стек **Backend:** - Node.js + Express - PostgreSQL + PostGIS - Redis - Docker **Frontend (Admin Panel):** - React + Vite - Tailwind CSS - React Leaflet (карты) - Axios ## 📋 Требования - Docker и Docker Compose - Node.js 22+ (для разработки без Docker и Firebase Admin SDK) - Статический IP или динамический DNS - ~2GB свободного места на диске ## 🛠 Установка и запуск ### 1. Клонирование и настройка ```bash # Создайте .env файл из примера cp .env.example .env # Настройте переменные окружения nano .env ``` Важные переменные в `.env`: ```env # Обязательно измените в продакшене! DB_PASSWORD=your_secure_password_here API_KEY_SALT=generate_random_salt_with_openssl ADMIN_PASSWORD=your_secure_admin_password_here # Для разработки/деплоя через встроенный proxy оставьте как есть CORS_ORIGIN=http://localhost:3001 API_URL=/api/v1 # Push отключён по умолчанию FCM_ENABLED=false # Для пассажирского режима backend проверяет Firebase ID token. # Укажите проект из google-services.json мобильного приложения и путь к service account JSON. FIREBASE_PROJECT_ID=your-firebase-project-id GOOGLE_APPLICATION_CREDENTIALS=/absolute/private/path/firebase-service-account.json ``` Для входа по passkey на публичном адресе укажите в `.env` точный домен панели и используйте HTTPS: `WEBAUTHN_RP_ID=admin.example.by` и `WEBAUTHN_ORIGIN=https://admin.example.by`. В `WEBAUTHN_ORIGIN` не применяются wildcard-маски; при нескольких разрешённых адресах перечислите их через запятую. Генерация безопасного salt: ```bash openssl rand -base64 32 ``` ### 2. Запуск с Docker (рекомендуется) ```bash # Сборка и запуск всех сервисов docker-compose up -d # Проверка статуса docker-compose ps # Просмотр логов docker-compose logs -f ``` Сервисы будут доступны: - **API**: http://localhost:3000 - **Admin Panel**: http://localhost:3001 - **PostgreSQL**: localhost:5432 - **Redis**: localhost:6379 Мастер-аккаунт создается автоматически при старте backend: - login: `admin` - password: значение `ADMIN_PASSWORD` из `.env` (если не задано, fallback `admin123`) ### 3. Проверка работоспособности ```bash # Проверка API curl http://localhost:3000/health # Проверка базы данных docker exec -it polotsk-transit-db psql -U transit_admin -d polotsk_transit -c "SELECT COUNT(*) FROM routes;" ``` ### 4. Разработка без Docker **Backend:** ```bash cd backend npm install cp ../.env.example .env # Убедитесь, что PostgreSQL и Redis запущены локально # Настройте DATABASE_URL и REDIS_URL в .env npm run dev ``` **Frontend:** ```bash cd frontend npm install cp .env.example .env npm run dev ``` ## 📱 Использование API ### Аутентификация Все запросы требуют API ключ в заголовке: ```bash curl -H "X-API-Key: dev_key_12345678" http://localhost:3000/api/v1/routes ``` ### Основные эндпоинты **Маршруты:** ```bash GET /api/v1/routes # Все маршруты GET /api/v1/routes/:id # Маршрут с остановками POST /api/v1/routes # Создать маршрут PUT /api/v1/routes/:id # Обновить маршрут DELETE /api/v1/routes/:id # Удалить маршрут ``` **Остановки:** ```bash GET /api/v1/stops # Все остановки GET /api/v1/stops/:id # Конкретная остановка GET /api/v1/stops/nearby?lat=55.4869&lon=28.7856&radius=500 # Ближайшие POST /api/v1/stops # Создать остановку PUT /api/v1/stops/:id # Обновить остановку DELETE /api/v1/stops/:id # Удалить остановку ``` **ETA (расчет времени прибытия):** ```bash POST /api/v1/eta/calculate { "stopId": 1, "routeId": 1 } GET /api/v1/eta/stop/:stopId # ETA для всех маршрутов на остановке ``` **Синхронизация (для offline-режима):** ```bash GET /api/v1/sync # Полная синхронизация GET /api/v1/sync?lastSync=2025-11-19T10:00:00Z # Инкрементальная GET /api/v1/sync/status # Статус системы ``` **Алерты:** ```bash GET /api/v1/alerts/active # Активные алерты для клиентов POST /api/v1/alerts # Создать алерт и, если он активен, отправить FCM ``` Настройка Firebase, контракт сообщения, идемпотентность и способы локальной проверки описаны в [документации FCM-уведомлений](docs/fcm-alert-notifications.md). ### Примеры запросов **Создание маршрута:** ```bash curl -X POST http://localhost:3000/api/v1/routes \ -H "X-API-Key: dev_key_12345678" \ -H "Content-Type: application/json" \ -d '{ "route_number": "1", "name": "Центр - Вокзал", "type": "bus", "color": "#0066CC", "description": "Основной маршрут через центр города" }' ``` **Создание остановки:** ```bash curl -X POST http://localhost:3000/api/v1/stops \ -H "X-API-Key: dev_key_12345678" \ -H "Content-Type: application/json" \ -d '{ "name": "Площадь Свободы", "latitude": 55.4869, "longitude": 28.7856, "address": "ул. Ленина, 1" }' ``` **Поиск ближайших остановок:** ```bash curl -H "X-API-Key: dev_key_12345678" \ "http://localhost:3000/api/v1/stops/nearby?lat=55.4869&lon=28.7856&radius=500" ``` ## 🔐 Безопасность домашнего сервера ### Обязательные меры безопасности 1. **Cloudflare Tunnel** (рекомендуется): ```bash # Установка cloudflared curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o cloudflared sudo mv cloudflared /usr/local/bin/ sudo chmod +x /usr/local/bin/cloudflared # Аутентификация cloudflared tunnel login # Создание туннеля cloudflared tunnel create polotsk-transit # Запуск cloudflared tunnel run polotsk-transit ``` 2. **Настройка SSL в Nginx:** ```bash # Получение сертификата Let's Encrypt sudo certbot --nginx -d api.yourdomain.com -d admin.yourdomain.com # Сертификаты автоматически обновятся ``` 3. **Fail2ban:** ```bash sudo apt install fail2ban sudo systemctl enable fail2ban sudo systemctl start fail2ban ``` 4. **Мониторинг:** ```bash # Установка Prometheus + Grafana (опционально) # Или простой мониторинг через Docker stats docker stats ``` ### Создание API ключей API ключи можно создать через базу данных: ```sql INSERT INTO api_keys (key, app_name, rate_limit) VALUES ('your_secure_key_here', 'iOS App', 10000); ``` Или через скрипт: ```javascript const crypto = require('crypto'); const apiKey = crypto.randomBytes(32).toString('hex'); console.log(apiKey); ``` ## 📊 Админ-панель Откройте http://localhost:3001 в браузере. **Функции:** - Управление маршрутами - Создание и редактирование остановок на карте - Просмотр статистики - Управление расписаниями (в разработке) ## 🗄 Структура базы данных ```sql routes # Маршруты stops # Остановки (с PostGIS геометрией) route_stops # Связь маршрутов и остановок schedules # Расписания vehicles # Транспортные средства historical_delays # Статистика задержек alerts # Уведомления api_keys # API ключи sync_log # Лог изменений для синхронизации ``` ## 🔄 Обновления ```bash # Остановка сервисов docker-compose down # Обновление кода git pull # Перезапуск docker-compose up -d --build ``` ## 🐛 Troubleshooting **База данных не стартует:** ```bash docker-compose logs postgres docker-compose down -v # Удалит volumes, ОСТОРОЖНО! docker-compose up -d ``` **API не отвечает:** ```bash docker-compose logs backend docker-compose restart backend ``` **Frontend не собирается:** ```bash cd frontend npm install npm run build ``` **PostGIS ошибки:** ```bash docker exec -it polotsk-transit-db psql -U transit_admin -d polotsk_transit SELECT PostGIS_Version(); ``` ## 📝 Следующие шаги (Roadmap) ### Версия 2.0 - Краудсорсинг GPS - [ ] Endpoint для получения GPS от пользователей - [ ] Агрегация и фильтрация GPS данных - [ ] WebSocket для реалтайм-обновлений - [ ] Функция "Я еду в маршруте" в приложениях ### Версия 3.0 - Продвинутые функции - [ ] IoT GPS-трекеры интеграция - [ ] Машинное обучение для предсказания задержек - [x] Push-уведомления о новых активных алертах - [ ] Аналитика и отчеты ## 🤝 Контакты и поддержка Создано для города Полоцка 🇧🇾 ## 📄 Лицензия MIT License - используйте свободно! ## Устройства RideBus Board В админке появился раздел **Устройства** (только для администратора). Он работает с обновлённым проектом `ridebus-board`: одноразовый код из четырёх цифр на экране привязывает отдельный браузер телевизора. В форме выбираются площадка и маршрут, тема/палитры, профиль читаемости, число строк, интервалы, информационные блоки и рекламный предпросмотр. Город задаётся переключателем транспортной сети в админке; устройства и каталоги изолированы по выбранному городу. Перед запуском примените **только новую** миграцию `backend/migrations/016_board_devices.sql` к существующей базе данных (обычным способом миграций при развёртывании). Не перезапускайте общий `npm run migrate` на уже созданной базе: существующий скрипт повторяет все старые SQL-файлы. Таблицы `board_pairings` и `board_devices` содержат одноразовые сессии и зарегистрированные устройства. Миграция 016 повторяемая. В сервере Board включите `BOARD_REMOTE_SETTINGS=true`, укажите обновлённый API в `RIDEBUS_API_BASE_URL` и адрес этой админки в необязательном `BOARD_ADMIN_URL`. Серверный RideBus API-ключ остаётся на Board и не выдаётся телевизору. Используйте HTTPS с Secure cookie. Новый узкий API монтируется до общей API-key авторизации: - `POST /api/v1/board-devices/pairings` — создать сессию, TTL 5 минут, до 10 запросов за 10 минут с одного IP. - `GET /api/v1/board-devices/self` и `POST /self/heartbeat` — только индивидуальный `X-Board-Token`. - `GET /api/v1/board-devices/admin` и `/admin/routes?stopId=…` — администратор + контекст города. - `POST /admin/claim` — код, название, настройки; до 5 попыток за 5 минут с одного IP. - `PUT /admin/:id` — настройки с ожидаемой `revision`; конфликт даёт 409. - `POST /admin/:id/revoke` — отозвать устройство в выбранном городе. Короткий код не является токеном доступа. Секрет — 32 случайных байта, в базе только хеш; занятие кода и создание устройства атомарны в одной транзакции. Повторный запрос устройства после потерянного HTTP-ответа узнаёт уже созданную привязку. В списках и конфигурации секреты не возвращаются. Табло опрашивает настройки раз в 15 секунд, отправляет сигнал работы раз в 30 и подтверждает применённую версию. «В сети» означает связь за последние 90 секунд. Офлайн-устройство продолжает работать со своим кешем, изменения остаются «Ожидают применения». Отзыв доступа действует после следующей связи с сервером. Команд перезагрузки ОС, удалённого shell или установки ПО нет. Проверки: `npm test --prefix backend`, `npm run build --prefix frontend`. Отдельный PostgreSQL-тест устройств запускается с `BOARD_DEVICE_TEST_DATABASE_URL` (имя БД должно содержать `test`); он создаёт и удаляет изолированную схему, не использует рабочие таблицы. Совместный визуальный стенд находится в `ridebus-board/test/fixtures/device-flow-server.mjs` и использует явные тестовые данные. ### Ночной режим табло В настройках устройства доступны `nightMode` (`auto` / `off`) и `nightWakeMinutes` (45 / 60). По умолчанию — автоматически, возврат за 60 минут. Обновлённый Board включает режим после завершения движения, если весь ночной перерыв до первого следующего рейса длиннее двух часов, только по полному действующему расписанию. Перезагрузка в середине перерыва сохраняет ночной экран до выбранного порога возврата 45/60 минут. Ночные продолжения рейсов, фильтр маршрута и отмены учитываются. В ночном экране остаются первый рейс, часы без секунд и сообщения о движении; цикл информации и рекламы приостанавливается. Дневная тема возвращается при выходе. Лимиты, привязка и подтверждение версии сохраняются. Поля находятся в существующем JSONB настроек устройств; новая SQL-миграция не требуется. Старые сохранённые конфигурации нормализуются при чтении (`auto / 60`) и не изменяются в базе автоматически. Для редактирования этих полей разверните обновлённые API, frontend админки и Board вместе. Блок информации «Дальше по маршруту» заменяет первый и последний рейс. Его переключатель сохраняет существующий ключ `infoServiceEnabled`: ранее отключённый блок остаётся отключённым, SQL-миграция и изменение JSON устройства не нужны. Схему следующих остановок и время пути строит обновлённый Board из полного кеша расписания; в админке изменено только название переключателя. ### Пассажирские блоки табло В форме устройства добавлены переключатели «До важных мест», «Оплата поездки», «Напоминание о последнем рейсе» и «Вероятность осадков». До трёх важных мест задаются конкретной остановкой назначения и коротким названием. По умолчанию места не выбраны; незаполненный блок на табло скрыт. Отправление, путь и прибытие рассчитывает Board из полного действующего расписания с порядком остановок и конечной конкретного рейса. Отмены, направление и переход через полночь учитываются. Тарифы по типу транспорта имеют поля `cityId`, `type` (`bus` / `minibus`), `price`, `payment`, `checkedAt`. Для Полоцка (город 1) пользователь подтвердил 11 октября 2026 года: автобус 1,05 BYN, маршрутка 2,10 BYN, оплата QR-кодом или наличными. Для других городов тарифы остаются незаполненными. Допустимы до 32 исключений по `routeId`; исключение имеет приоритет над тарифом типа. Цена и способы оплаты — подтверждённый оператором текст, дата проверки сохраняется и видна пассажиру. Будущий тариф не публикуется до указанной даты. Платёжный QR-код не генерируется. Конфигурация: `infoPlacesEnabled`, `infoFareEnabled`, `lastTripNoticeEnabled`, `weatherHintEnabled` (по умолчанию `true`); `places: [{stopId, name}]`; `fareByType: [{cityId, type, price, payment, checkedAt}]`; `fareRules: [{routeId, price, payment, checkedAt}]`. API проверяет структуру, лимиты, дубликаты, действительность даты и принадлежность городу/площадке. Явные пустые массивы сохраняются. Старые конфигурации нормализуются без записи в БД; ранее отсутствовавший тариф типа подставляется только для города 1. Новые поля хранятся в существующем JSONB; дополнительная SQL-миграция не нужна. Разверните обновлённые backend, frontend админки и RideBus Board вместе. Переключатели и содержимое передаются через существующие версии настроек и подтверждения устройством. Проверки пассажирских настроек входят в `test/boardDevices.test.js` и `test/boardDevicesApi.test.js`; визуальный стенд `ridebus-board/test/fixtures/passenger-server.mjs` использует отдельный локальный origin и тестовое расписание.