Сервіси

Бекенд Trans-Atlas: Спринт 0

План переведення демонстраційного SPA на застосунок із серверним джерелом істини: акаунти компаній, персистентні заявки, довідники в БД, журнал подій.

Це специфікація й запис уже проведених перевірок артефактів, а не підключений бекенд: index.html і зараз лишається односторінковим файлом без збірки, без PostgreSQL, без NestJS-сервера і без реального API. Розділ «Зміни на фронтенді» описує наступний крок, а не те, що вже застосовано — LISTINGS, вхід через localStorage і test-data/ й далі працюють так само, як до цієї сторінки.

Входить у Спринт 0

  • PostgreSQL 18 + PostGIS: схема, міграції, довідники з реєстрів SPA
  • Автентифікація: реєстрація компанії, вхід, ротація refresh-токенів, підтвердження email, ролі
  • CRUD заявок і стрічка з фільтрами, що повторюють filteredListings()
  • Імпорт наявних 10 773 демо- і тестових заявок у БД без втрати даних
  • Append-only журнал подій
  • Розгортання, міграції, бекапи, моніторинг

Не входить свідомо

Що Чому Куди перенесено
Модуль SEARCH (маршрути, магістраль→фідери, POST_DRONE)Власний REST-контракт і власний CIОкремий етап
Верифікація контрагента (KYB, реєстри, дельта-моніторинг)Напрям A плануТижні 3–7
Ingest-агентНапрям B плануТижні 4–8
SSE-стрічка, push, PWAНапрям C плануТижні 6–10
Ставки, торг, ескроу, розрахункиМежа модулів A.1.3 рамкового ТЗПісля перших угод

Стек і середовище

Компонент Рішення Чому
МоваTypeScript, Node.js 24 LTSОдна мова від схеми до інтерфейсу
КаркасNestJSМодульність, DI, готова інтеграція валідації та OpenAPI
БДPostgreSQL 18 + PostGIS 3.6PostGIS потрібен уже зараз для країн і відстаней
Кеш і чергаRedis 7Ідемпотентність, ліміти запитів, надалі шина подій стрічки
Міграціїnode-pg-migrate або Flyway, тільки впередЗастосовність до продової бази без ручних кроків
ПоштаSMTP із SPF/DKIM/DMARCПідтвердження email і скидання пароля
СпостережуваністьJSON-логи, /api/v1/health, PrometheusМінімум, без якого продакшн не запускається

Ключові інваріанти на рівні БД

Інваріант Механізм
mode='multi' ⇒ components містить ≥ 2 плеча, лише базові видиCHECK listings_components_rule
mode<>'multi' ⇒ components IS NULLтой самий CHECK
mode='drone' ⇔ існує рядок у listing_drone_detailsвідкладений CONSTRAINT TRIGGER
Місто відправлення ≠ місто призначенняCHECK listings_origin_ne_destination
is_cross_border не редагується вручнуGENERATED ALWAYS AS ... STORED
Рівень верифікації вище L0 неможливий без дати перевіркиCHECK companies_verified_needs_date
У компанії рівно один власникчастковий UNIQUE INDEX по role='owner'
Email унікальний без урахування регіструтип citext
Журнал подій не редагується застосункомREVOKE UPDATE, DELETE для ролі застосунку

Паритет фільтрів API і SPA

Стрічка GET /api/v1/listings мусить давати той самий результат, що й filteredListings() сьогодні.

state.filters у SPA Параметр API
state.filters.modemode
state.filters.multiModescomponents
state.filters.fromorigin_city_id
state.filters.todestination_city_id
state.filters.fromCountryorigin_country
state.filters.toCountrydestination_country
state.filters.borderScopeborder_scope
state.filters.currencycurrency

Автентифікація і безпека

Вимога Значення
Хешування пароляargon2id, m=64 MiB, t=3, p=1
Мінімальна довжина пароля10 символів; перевірка за списком скомпрометованих
Access-токенJWT, 15 хв, EdDSA, kid для ротації ключів
Refresh-токен30 днів, лише SHA-256; ротація з родиною, повторне використання вбиває всі сесії
Блокування10 невдалих спроб → блок на 15 хвилин
Підтвердження emailОдноразовий токен, 24 години; до підтвердження - перегляд без публікації
Роліowner, manager, viewer; редагувати заявку може лише її компанія

Нефункціональні вимоги

Показник Ціль
Стрічка з фільтрами, p95≤ 200 мс на 100 тис. заявок
Публікація заявки, p95≤ 300 мс
Доступність99.5% на місяць
БекапЩодоби повний, WAL безперервно, відновлення - щомісяця
МіграціїТільки вперед, без ручних кроків

Фактичні прогони

Прогін Що перевіряв Результат
tools/extract_registries.pyВитягування реєстрів із index.html175 міст, 9 вантажів, 32 валюти, порожніх полів немає
ci/check_backend_contract.py8 груп перевірок артефактів15 із 15 пройдено
ci/acceptance_db.pyЖива PostgreSQL 18.6 + PostGIS 3.625 із 25 пройдено

Заплановані зміни на фронтенді

Наступний крок після появи реального бекенду — не застосовано в цьому демо:

  • LISTINGS перестає бути літералом і стає результатом GET /api/v1/listings; loadExternalListings() і LISTINGS_FEED замінюються курсорною пагінацією
  • getUsers(), setUsers(), saveUser() видаляються. localStorage лишається лише для мови, вибраного виду транспорту та інших втрачуваних станів UI
  • Форма модального вікна отримує третій режим — публікацію заявки
  • Помилки показуються з problem+json, поле в помилці підсвічується за JSON Pointer
  • Перемикач API_BASE: за відсутності бекенду сайт чесно повідомляє про це, як уже роблять вкладки погоди й кордонів

CI-гейти: обов'язкові перевірки

Дванадцять обов'язкових перевірок від трьох конвеєрів; кожен конвеєр має власний gate-job, що падає, якщо впав будь-який job, від якого він залежить.

Конвеєр Job Перевірка в правилі
.github/workflows/contract.yml schema Схема і сценарії T16-T27
i18n Валютні та мовні сценарії C1-C13, L1-L12
proto Контракт proto модуля SEARCH
ui UI-сценарії U1-U8 вибору виду транспорту
contract-gate contract-gate
.github/workflows/backend.yml backend-contract Контракт бекенду Спринту 0
backend-db Приймальні тести БД Спринту 0
verification-contract Контракт модуля верифікації
verification-db Інваріанти верифікації
backend-gate backend-gate
.github/workflows/web3.yml web3-tests web3-sprint0-tests
blockchain-gate blockchain-gate

Це застосована конфігурація, а не дизайн: правило гілки main вимагає всі дванадцять перевірок, названі в таблиці вище, а `.github/workflows/` містить три обов'язкові конвеєри — `contract.yml`, `backend.yml` і `web3.yml` — плюс неблокувальний `source-probe.yml`. Скрипти `sprint-0-backend/ci/*`, які викликає конвеєр бекенду, лежать у репозиторії й виконуються на живій PostgreSQL з PostGIS: у прогоні на main пройшли міграція, засів довідників, імпорт 10773 заявок і негативні тести інваріантів — разом 42 перевірки на живій БД. `ci/check_live_ruleset.py` звіряє файл правила з живим GitHub і на поточному стані main дає збіг: 12 перевірок у файлі, 12 у GitHub. Перевірка узгодженості `ci/check_ruleset.py` теж виконується: 14 правил, 35 із 35 мутацій виявлено. Не застосовано іншого: сама ця сторінка й далі односторінковий файл без бекенду — конвеєри перевіряють контракти й БД, а не обслуговують index.html.

Файли моделі