Skip to content

Repository files navigation

dtpstat-map-server

Node.js/Express + PostgreSQL/PostGIS сервер интерактивной карты линейных объектов. Код рассчитан на несколько независимых экземпляров: разные БД, схемы, порты, данные, расчёты и оформление используют один runtime.

Требования:

  • Node.js 20.19+ (для production рекомендуется Node.js 24.x);
  • PostgreSQL;
  • PostGIS.

Возможности

  • публичная Mapbox-карта с viewport-загрузкой линий;
  • города и административные границы из OSM/Overpass;
  • пакетная загрузка place=city/town и настраиваемого диапазона boundary=administrative;
  • дерево вложенности OSM-полигонов, ручные active/displayName/displayType и Mapbox-preview;
  • импорт 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 start

Для локальной PostgreSQL из compose.yaml:

docker compose up -d database
npm run db:init
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 меняется через админку/перенос настроек.

Архитектура разработки

Архитектурные границы проекта являются частью контракта разработки, а не рекомендацией. Каноническое описание слоёв, правил зависимостей и размещения нового кода находится в docs/architecture.md. Инструкции для coding agents находятся в корневом AGENTS.md.

Перед отправкой изменений можно отдельно проверить архитектурные ограничения:

npm run test:architecture

Полная обязательная проверка остаётся:

npm run check

Экземпляры

Рекомендуемая модель:

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…V033.

Последние изменения:

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
V027 OSM object identity, active/display identity, hierarchy, DB-backed OSM import settings и пороги large/small
V028 раздельные лимиты одного Overpass response / всей загрузки и база для adaptive geometry batching
V029 durable checkpoint + persistent geometry staging для возобновления OSM update после ошибки/рестарта
V030 накопительный счётчик фактически сохранённых geometry batches в checkpoint
V031 диагностика OSM-объектов без построенной geometry в resumable checkpoint
V032 population/asOf/source/attributes на CITY_BOUNDARIES и активная проекция в CITY_POPULATIONS
V033 вертикальное key/value-хранилище REPORT_CONFIG вместо растущей singleton-строки
V034 настраиваемая политика паролей администраторов
V035 отдельное право редактора OSM-дерева

Следующая migration: V036+. Уже опубликованные migrations не редактируются задним числом.

История хранится в:

<DATABASE_SCHEMA>.schema_versions

npm start автоматически применяет все pending migrations из db/migrations до bootstrap и открытия HTTP/HTTPS listeners. Migration runner использует PostgreSQL advisory lock, поэтому параллельные старты одного schema не применяют одну migration дважды. Checksum/history по-прежнему проверяются; при modified/gapped/newer history или SQL-ошибке startup завершается и приложение не начинает обслуживать запросы. npm run db:migrate остаётся доступной ручной preflight-командой.

Большие portable JSON / ZIP transfers

Admin transfer для OSM boundaries, линий и населения поддерживает raw JSON/GeoJSON и single-entry ZIP/ZIP64. Экспорт формируется потоково; импорт принимает в том числе chunked ZIP из pipe/stdin и затем разбирает JSON по элементам без materialization всего документа в heap Node. Directory entries игнорируются, после них должна остаться ровно одна data entry; её имя и расширение не используются для определения JSON schema.

DB import выполняется одной транзакцией: malformed JSON/ZIP, schema/PostGIS ошибка или cancellation приводят к полному ROLLBACK. Лимиты streaming transport/decoded JSON/item, ZIP ratio/entry count и JSON depth/record count задаются через IMPORT_API_MAX_STREAM_*. Подробнее: docs/data-transfer.md.

OSM геометрии

Начиная с V027, исходная identity каждого объекта — строго:

OSM_TYPE + OSM_ID

Разные relations больше никогда не объединяются по совпадению имени. Историческая нормализация V023 отключена новой migration; после перехода на V027 рекомендуется один раз заново выполнить OSM update, чтобы восстановить объекты, ранее потерянные из-за name-based merge.

Загрузчик получает ID-индекс, дедуплицирует пересечения selectors по (osm_type, osm_id), затем последовательно загружает geometry batches. Начиная с V028 лимит памяти одного Overpass-ответа отделён от суммарного лимита операции. Если geometry batch превышает single-response limit, он автоматически делится пополам и повторяется.

Начиная с V029 индекс OSM и успешно проверенные geometry batches сохраняются в PostgreSQL как durable checkpoint. После ошибки, отмены или перезапуска Node администратор может явно выбрать «Возобновить»: уже staged объекты повторно не скачиваются. Production CITY_BOUNDARIES при этом остаётся неизменной до полного snapshot и одной финальной транзакции. «Запустить заново» при наличии checkpoint требует явного подтверждения; старый checkpoint сохраняется до успешного получения нового индекса.

Для каждого объекта отдельно хранятся source-признаки OSM и пользовательская конфигурация:

  • PLACE_TYPE / ADMIN_LEVEL;
  • IS_ACTIVE;
  • DISPLAY_NAME / DISPLAY_TYPE;
  • PARENT_ID, вычисленный по полному ST_Covers(parent, child);
  • AREA_M2.

Только активные boundaries участвуют в привязке населения, линий, публичной карте и отчётах. Просто пересекающиеся полигоны не образуют parent/child связь.

Основные таблицы

В <DATABASE_SCHEMA> используются:

  • cities;
  • city_populations;
  • city_boundaries;
  • city_geometries;
  • line_types;
  • project_settings;
  • report_config;
  • city_report_values;
  • osm_import_settings;
  • 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.

Публичные GeoJSON/CSV

В настройке задаётся только базовое имя без расширения. Например:

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 разделён на:

  1. города/OSM boundaries — GeoJSON;
  2. линии + business line types — GeoJSON/KML;
  3. население — 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 7.

Импорт принимает v1…v7 и нормализует legacy fields. V5 добавил rank.sort, V6 — publicDownloadName, V7 — пороги разделения больших/малых городов.

Переносятся 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.

Reverse proxy / nginx

Для одного доверенного nginx:

HOST=127.0.0.1
HTTP_TRUST_PROXY_HOPS=1
location / {
    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 scripts

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
npm run test:integration

PostgreSQL/PostGIS integration regression

Обычный npm test не подключается к PostgreSQL. Для проверки реальных migrations, PostGIS SQL, project-settings transfer repository/service, временного line-type staging и spatial/cursor export используется отдельный opt-in harness:

npm run test:integration

Harness подключается к существующей БД, указанной в .env: DATABASE_HOST, DATABASE_PORT, DATABASE_NAME и SSL-настройки берутся из database config, а подключение выполняется административной ролью PostgreSQL из POSTGRES_ADMIN_USER / POSTGRES_ADMIN_PASSWORD.

Рабочая DATABASE_SCHEMA приложения не используется и не изменяется. Для каждого запуска создаётся случайная schema dtpstat_it_*, в неё применяются все migrations и выполняются integration checks. В finally временная schema удаляется через DROP SCHEMA ... CASCADE. PostGIS ожидается уже установленным в этой БД штатным npm run db:init.

Импорт и перенос application data выполняются через административные API/UI. Отдельного repository-snapshot import script нет.

Документация

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages