Υπηρεσίες
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.6 | PostGIS потрібен уже зараз для країн і відстаней |
| Кеш і черга | 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.mode | mode |
state.filters.multiModes | components |
state.filters.from | origin_city_id |
state.filters.to | destination_city_id |
state.filters.fromCountry | origin_country |
state.filters.toCountry | destination_country |
state.filters.borderScope | border_scope |
state.filters.currency | currency |
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.html | 175 міст, 9 вантажів, 32 валюти, порожніх полів немає |
ci/check_backend_contract.py | 8 груп перевірок артефактів | 15 із 15 пройдено |
ci/acceptance_db.py | Жива PostgreSQL 18.6 + PostGIS 3.6 | 25 із 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