Files
ridebus-admin/README.md
Zakhar Timoshenko 360b684019 feat: implement board device service with CRUD operations and validation
- Add boardDeviceService for managing device pairing, status, claiming, and revocation.
- Introduce boardDeviceSettings for default settings and validation logic.
- Create integration tests for device lifecycle, concurrent claims, and updates.
- Add unit tests for device pairing, claiming, and settings validation.
- Implement API tests for device endpoints with JWT authentication.
- Develop in-memory repository for testing purposes.
- Create frontend components for managing devices, including a form for device settings.
- Style DevicesPage with CSS for better user experience.
2026-10-10 00:47:26 +03:00

16 KiB
Raw Blame History

RideBus DE API

Система управления информацией об общественном транспорте города Полоцка с REST API, веб-панелью администратора и поддержкой offline-синхронизации.

🚀 Возможности

MVP (Версия 1.0 - статические данные)

  • ✅ REST API для маршрутов и остановок
  • ✅ Геопространственные запросы (PostGIS)
  • ✅ Расчет ETA на основе расписания
  • ✅ Offline-синхронизация для мобильных приложений
  • ✅ Веб-панель администратора
  • ✅ Кеширование через Redis
  • ✅ Аутентификация по API ключам
  • ✅ Rate limiting
  • ✅ Серверные push-уведомления об алертах через Firebase Cloud Messaging
  • ✅ Импорт отмен из Telegram через отдельный аккаунт, проверка в админке и срок действия до полуночи (настройка)

Технологический стек

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. Клонирование и настройка

# Создайте .env файл из примера
cp .env.example .env

# Настройте переменные окружения
nano .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:

openssl rand -base64 32

2. Запуск с Docker (рекомендуется)

# Сборка и запуск всех сервисов
docker-compose up -d

# Проверка статуса
docker-compose ps

# Просмотр логов
docker-compose logs -f

Сервисы будут доступны:

Мастер-аккаунт создается автоматически при старте backend:

  • login: admin
  • password: значение ADMIN_PASSWORD из .env (если не задано, fallback admin123)

3. Проверка работоспособности

# Проверка 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:

cd backend
npm install
cp ../.env.example .env

# Убедитесь, что PostgreSQL и Redis запущены локально
# Настройте DATABASE_URL и REDIS_URL в .env

npm run dev

Frontend:

cd frontend
npm install
cp .env.example .env

npm run dev

📱 Использование API

Аутентификация

Все запросы требуют API ключ в заголовке:

curl -H "X-API-Key: dev_key_12345678" http://localhost:3000/api/v1/routes

Основные эндпоинты

Маршруты:

GET    /api/v1/routes              # Все маршруты
GET    /api/v1/routes/:id          # Маршрут с остановками
POST   /api/v1/routes              # Создать маршрут
PUT    /api/v1/routes/:id          # Обновить маршрут
DELETE /api/v1/routes/:id          # Удалить маршрут

Остановки:

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 (расчет времени прибытия):

POST /api/v1/eta/calculate
{
  "stopId": 1,
  "routeId": 1
}

GET /api/v1/eta/stop/:stopId  # ETA для всех маршрутов на остановке

Синхронизация (для offline-режима):

GET /api/v1/sync                           # Полная синхронизация
GET /api/v1/sync?lastSync=2025-11-19T10:00:00Z  # Инкрементальная
GET /api/v1/sync/status                    # Статус системы

Алерты:

GET  /api/v1/alerts/active  # Активные алерты для клиентов
POST /api/v1/alerts         # Создать алерт и, если он активен, отправить FCM

Настройка Firebase, контракт сообщения, идемпотентность и способы локальной проверки описаны в документации FCM-уведомлений.

Примеры запросов

Создание маршрута:

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": "Основной маршрут через центр города"
  }'

Создание остановки:

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"
  }'

Поиск ближайших остановок:

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 (рекомендуется):
# Установка 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
  1. Настройка SSL в Nginx:
# Получение сертификата Let's Encrypt
sudo certbot --nginx -d api.yourdomain.com -d admin.yourdomain.com

# Сертификаты автоматически обновятся
  1. Fail2ban:
sudo apt install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
  1. Мониторинг:
# Установка Prometheus + Grafana (опционально)
# Или простой мониторинг через Docker stats
docker stats

Создание API ключей

API ключи можно создать через базу данных:

INSERT INTO api_keys (key, app_name, rate_limit) 
VALUES ('your_secure_key_here', 'iOS App', 10000);

Или через скрипт:

const crypto = require('crypto');
const apiKey = crypto.randomBytes(32).toString('hex');
console.log(apiKey);

📊 Админ-панель

Откройте http://localhost:3001 в браузере.

Функции:

  • Управление маршрутами
  • Создание и редактирование остановок на карте
  • Просмотр статистики
  • Управление расписаниями (в разработке)

🗄 Структура базы данных

routes          # Маршруты
stops           # Остановки (с PostGIS геометрией)
route_stops     # Связь маршрутов и остановок
schedules       # Расписания
vehicles        # Транспортные средства
historical_delays  # Статистика задержек
alerts          # Уведомления
api_keys        # API ключи
sync_log        # Лог изменений для синхронизации

🔄 Обновления

# Остановка сервисов
docker-compose down

# Обновление кода
git pull

# Перезапуск
docker-compose up -d --build

🐛 Troubleshooting

База данных не стартует:

docker-compose logs postgres
docker-compose down -v  # Удалит volumes, ОСТОРОЖНО!
docker-compose up -d

API не отвечает:

docker-compose logs backend
docker-compose restart backend

Frontend не собирается:

cd frontend
npm install
npm run build

PostGIS ошибки:

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-трекеры интеграция
  • Машинное обучение для предсказания задержек
  • 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 и использует явные тестовые данные.