Palvelut

Trans-Atlas backend: Sprint 0

The plan for turning the demo SPA into an app with a server-side source of truth: company accounts, persistent listings, database-backed reference data, an event log.

This is a specification and a record of artifact checks already run, not a connected backend: index.html is still a single-page file with no build step, no PostgreSQL, no NestJS server, and no real API. The "Planned frontend changes" section describes the next step, not what's already applied - LISTINGS, the localStorage login, and test-data/ keep working exactly as before this page.

In scope for Sprint 0

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

Deliberately out of scope

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

Stack and environment

Component Decision Why
Мова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Мінімум, без якого продакшн не запускається

Key database-level invariants

Invariant Mechanism
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 filter parity

GET /api/v1/listings must return the same result as today's filteredListings().

SPA state.filters API parameter
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

Authentication and security

Requirement Value
Хешування пароля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; редагувати заявку може лише її компанія

Non-functional requirements

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

Actual test runs

Run What it checked Result
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 пройдено

Planned frontend changes

The next step once a real backend exists - not applied in this demo:

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

CI gates: required checks

Twelve required checks from three pipelines; each pipeline has its own gate job, which fails if any job it depends on fails.

Pipeline Job Check in the ruleset
.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

This is an applied configuration, not a design: the main-branch ruleset requires all twelve checks named in the table above, and `.github/workflows/` holds three required pipelines - `contract.yml`, `backend.yml` and `web3.yml` - plus the non-blocking `source-probe.yml`. The `sprint-0-backend/ci/*` scripts the backend pipeline calls live in the repository and run against a real PostgreSQL with PostGIS: the run on main passed the migration, registry seeding, the import of 10773 listings and the negative invariant tests - 42 checks on a live database. `ci/check_live_ruleset.py` diffs the ruleset file against live GitHub and reports a match on the current main: 12 checks in the file, 12 on GitHub. The consistency checker `ci/check_ruleset.py` runs too: 14 rules, 35 of 35 mutations caught. What is still not applied: this page itself remains a single file with no backend - the pipelines check contracts and the database, they do not serve index.html.

Model files