Node.js/Express + PostgreSQL/PostGIS сервер интерактивной карты линейных объектов. Код рассчитан на несколько независимых экземпляров: разные БД, схемы, порты, данные, расчёты и оформление используют один runtime.
Требования:
- Node.js
20.19+(для production рекомендуется Node.js24.x); - PostgreSQL;
- PostGIS.
- публичная Mapbox-карта с viewport-загрузкой линий;
- города и границы из OSM/Overpass;
- объединение частей одного OSM
relationв один логический город; - импорт GeoJSON, KML и Google My Maps;
- переносимый GeoJSON/KML со словарём
LINE_TYPES; - сохранение
<Placemark><name>какproperties.placemarkName; - независимые постоянные подписи линий и hover-popup;
- декларативные расчётные метрики без произвольного SQL;
- последовательный рейтинг по нескольким метрикам;
- настраиваемые публичная таблица и CSV;
- условное форматирование числовых колонок;
- темы
retro,classic,modern; - DB-backed Mapbox public token;
- настраиваемый PNG-маркер городов;
- настраиваемое базовое имя публичных GeoJSON/CSV;
- DB-backed Yandex Metrica и Google Analytics 4 с CSP-safe ранней загрузкой;
- DB-backed пользователи, роли, sessions, profile/avatar, IP/account lockout и audit с конкретным before/after change-set для несекретных admin-изменений;
- WebSocket-журнал и single-task guard для длительных операций управления данными;
- HTTP/HTTPS и deployment за reverse proxy.
npm ci
cp .env.example .env
# заполнить .env
npm run db:init
npm run db:migrate
npm startДля локальной PostgreSQL из compose.yaml:
docker compose up -d database
npm run db:init
npm run db:migrate
npm startПри первом старте пустая ADMIN_USERS получает bootstrap-superuser из:
IMPORT_API_USERNAME=admin
IMPORT_API_PASSWORD=replace-with-a-long-random-passwordПосле появления DB-пользователя эти ENV credentials не являются login fallback. Они могут оставаться только как recovery source для npm run admin:set-superuser.
MAPBOX_ACCESS_TOKEN начиная с V019 используется только для одноразового bootstrap DB-настройки. После инициализации token меняется через админку/перенос настроек.
Рекомендуемая модель:
1 экземпляр приложения
= 1 PostgreSQL database
= 1 DATABASE_SCHEMA
= 1 HTTP/HTTPS port set
DATABASE_SCHEMA — SQL schema и технический namespace. Runtime SQL использует search_path=<schema>,public; жёсткие ссылки buslanes.<table> в application code недопустимы.
Пример второго экземпляра:
DATABASE_NAME=tramlanes
DATABASE_ROLE=tramlanes
DATABASE_SCHEMA=tramlanes
HOST=127.0.0.1
HTTP_ENABLED=true
HTTP_PORT=3002Исторические migration files могут содержать token BUSLANES: migration runner заменяет его на фактический DATABASE_SCHEMA перед выполнением.
Подробнее: docs/deployment.md.
Текущая последовательность: V001…V026.
Последние изменения:
| Migration | Назначение |
|---|---|
V018 |
sessions, роли, profile/avatar, IP security, audit indexes |
V019 |
Mapbox token в PROJECT_SETTINGS |
V020 |
custom city marker |
V021 |
public theme preset |
V022 |
независимый hover-popup имени линии |
V023 |
CITY_BOUNDARIES.FULL_NAME, объединение частей OSM relation и синхронизация CITIES.FULL_NAME |
V024 |
последовательный multi-column ranking (REPORT_CONFIG.RANK_SORT) |
V025 |
PROJECT_SETTINGS.PUBLIC_DOWNLOAD_NAME |
V026 |
динамические ссылки на публичные GeoJSON/CSV в footer |
Следующая migration: V027+. Уже опубликованные migrations не редактируются задним числом.
История хранится в:
<DATABASE_SCHEMA>.schema_versions
CITY_BOUNDARIES.FULL_NAME вычисляется как первое непустое значение:
addr:district
→ name:ru
→ osm_name
Для OSM_TYPE='relation' строки с одинаковыми:
PLACE_TYPE + FULL_NAME
считаются частями одного логического города и объединяются в MultiPolygon.
OSM_TYPE/OSM_ID пока сохраняются как provenance и используются portable city-transfer для восстановления связей. Это не identity логического города после нормализации relation fragments.
В <DATABASE_SCHEMA> используются:
cities;city_populations;city_boundaries;city_geometries;line_types;project_settings;report_config;city_report_values;admin_users;admin_sessions;admin_security_settings;admin_login_ip_state;admin_blocked_ips;admin_audit_log;admin_task_successes;- operational journals OSM/KML updates.
Web-admin: /admin/.
Права:
CAN_MANAGE_DATA
CAN_MANAGE_INTERFACE
CAN_MANAGE_USERS
CAN_VIEW_AUDIT
CAN_MANAGE_SECURITY
IS_SUPERUSER
Web UI использует HttpOnly session cookie. HTTP Basic остаётся для scripted API.
Длительные mutating data operations выполняются через process-local single-task manager. Один экземпляр Node не должен блокировать задачи другого экземпляра/БД.
Подробнее: docs/admin-security.md.
PROJECT_SETTINGS содержит, среди прочего:
PROJECT_NAME;KEYWORDS;- валидируемый
FOOTER_HTML; - analytics IDs;
THEME_PRESET;SHOW_LINE_LABELS;SHOW_LINE_POPUPS;- Mapbox public token;
- custom city marker;
PUBLIC_DOWNLOAD_NAME.
SHOW_LINE_LABELS и SHOW_LINE_POPUPS независимы.
Analytics IDs подключаются только когда заданы. Счётчики загружаются ранним внешним скриптом в <head>; CSP разрешает официальные endpoints Yandex Metrica/Session Replay и GA4. Диагностика загрузчиков доступна в браузере через window.dtpstatMetrics. Подробнее: docs/analytics.md.
В настройке задаётся только базовое имя без расширения. Например:
tram-lines
полностью определяет:
var/public-downloads/tram-lines.geojson
var/public-downloads/tram-lines.csv
/tram-lines.geojson
/tram-lines.csv
Content-Disposition: tram-lines.geojson / tram-lines.csv
При смене имени snapshots сразу пересобираются. Старые .csv/.geojson в var/public-downloads/ удаляются; старые URL не сохраняются как aliases.
Footer может использовать placeholders:
{{PUBLIC_GEOJSON_URL}}
{{PUBLIC_CSV_URL}}
Они подставляются при рендеринге страницы из текущего PUBLIC_DOWNLOAD_NAME.
var/public-downloads/ — runtime state, а не backup/source bundle. Статические source snapshots в корне репозитория не используются и не хранятся.
В Настройка интерфейса → Расчёты задаются:
- metrics;
- публичные table columns;
- CSV columns;
- ranking.
Backend компилирует только server-owned DSL: fields, aggregates, references на другие metrics, constants и arithmetic operations. Произвольный SQL не принимается.
Рейтинг поддерживает до восьми уникальных критериев:
{
"rank": {
"sort": [
{ "metricKey": "separation_ratio", "direction": "desc" },
{ "metricKey": "network_length_m", "direction": "desc" },
{ "metricKey": "population", "direction": "asc" }
]
}
}Критерии применяются последовательно; финальный deterministic fallback — city.name ASC. Ranking по-прежнему считается отдельно для больших/малых городов.
Подробнее: docs/report-config.md.
Admin data-transfer разделён на:
- города/OSM boundaries — GeoJSON;
- линии + business line types — GeoJSON/KML;
- население — JSON.
Рекомендуемый порядок для нового экземпляра:
cities → lines → populations
Public snapshots не являются round-trip format. Для переноса используйте /api/admin/export/* и соответствующие import endpoints.
Подробнее: docs/data-transfer.md и docs/kml-transfer.md.
Superuser API:
GET /api/admin/settings/export
POST /api/admin/settings/import
Текущий package: project-settings, schemaVersion 6.
Импорт принимает v1…v6 и нормализует legacy fields. V5 добавил rank.sort, V6 — publicDownloadName.
Переносятся project settings, line types, report config, security policy и public Mapbox token. Не переносятся users/password hashes/sessions/audit, source data, .env, TLS/DB secrets и custom city marker binary.
Подробнее: docs/project-settings-transfer.md.
Для одного доверенного nginx:
HOST=127.0.0.1
HTTP_TRUST_PROXY_HOPS=1location / {
proxy_pass http://127.0.0.1:3001;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}Для стандартного application upload limit 25 MiB:
client_max_body_size 30m;Подробнее: docs/deployment.md.
npm start
npm run dev
npm run db:init
npm run db:migrate
npm run admin:unblock
npm run admin:set-superuser
npm run lint
npm test
npm run check
Импорт и перенос application data выполняются через административные API/UI. Отдельного repository-snapshot import script нет.
- deployment.md — экземпляры, migrations, nginx/PM2;
- admin-security.md — auth/roles/sessions/audit/IP security;
- data-transfer.md — cities/lines/populations;
- kml-transfer.md — portable KML;
- report-config.md — metrics/table/CSV/ranking;
- project-settings-transfer.md — перенос конфигурации;
- database-indexes.md — актуальные indexes/access paths.