| .sqlx | ||
| migrations | ||
| src | ||
| tests | ||
| .gitignore | ||
| .sops.yaml | ||
| Cargo.lock | ||
| Cargo.toml | ||
| compose.yaml | ||
| flake.lock | ||
| flake.nix | ||
| justfile | ||
| README.md | ||
| secrets.yaml | ||
| test_multiberry.sh | ||
Multiberry Backend
Веб-приложение для хакатона ВТБ. Backend на Rust + Axum, БД — PostgreSQL.
🚀 Быстрый старт (macOS)
1. Установка зависимостей
при условии что docker, sops, age уже установлены
just - это Makefile 2.0
brew install just
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup --version
cargo --version
# если cargo ругается
rustup default stable
# если же нет, все прекрасно, curl и rustup уже сделали свое дело
2. Клонируй репозиторий
git clone ssh://git@gitverse.ru:2222/rorikstr/multiberry-backend.git
cd multiberry-backend
3. Запусти проект
Короч. Чтобы не запускать бесконечное число раз sops exec-env для вскрытия secrets.yaml я придумал выполнять проект на Makefile 2.0: justfile
# Посмотри все доступные команды
just
# Запусти базу данных
just db-up
# В новом терминале запусти бэкенд
just run
# Проверь, что всё работает
just health
Ожидаемый результат:
{"status":"Database connection is successful."}
✅ Готово! Бэкенд работает на http://localhost:3000
📋 Основные команды
| Команда | Описание |
|---|---|
just run |
Запустить бэкенд в dev режиме |
just db-up |
Поднять PostgreSQL |
just db-down |
Остановить PostgreSQL |
just db-reset |
Пересоздать БД (удалит все данные!) |
just build |
Собрать релизную версию |
just test |
Запустить тесты |
just health |
Проверить здоровье API |
just db-logs |
Показать логи БД |
just stop |
Остановить всё |
Больше информации: just --list
🔐 Секреты и переменные окружения
Все секреты (пароли, API ключи) хранятся в зашифрованном файле secrets.yaml.
Как это работает:
justкоманды автоматически расшифровываютsecrets.yamlперед запуском
Если нужно отредактировать секреты (добавить новый API ключ и т.д.):
sops secrets.yaml
🛠️ Разработка
Структура проекта
.
├── src/
│ └── main.rs # Основной код приложения
├── Cargo.toml # Зависимости Rust
├── Cargo.lock # Зафиксированные версии зависимостей
├── compose.yaml # Описание Docker контейнера с PostgreSQL
├── secrets.yaml # Зашифрованные секреты (в Git, не редактируй вручную!)
├── .sops.yaml # Конфигурация sops (управление ключами)
├── justfile # Команды проекта
└── README.md # Этот файл
Workflow разработки
-
Создай фича-ветку:
git checkout -b feature/my-feature -
Запусти проект:
just db-up just run -
Внеси изменения в
src/main.rs -
Тесты:
just test -
Push и создай Pull Request
❓ Troubleshooting
"Connection reset by peer" при запуске
БД еще инициализируется. Подожди 5-10 секунд и попробуй снова:
just run
"password authentication failed"
Вероятно, в secrets.yaml неверный пароль. Напиши Wave.
Docker Desktop не запускается
Убедись, что Docker Desktop открыт:
open /Applications/Docker.app
just db-logs # Логи БД
docker ps # Список запущенных контейнеров
API Documentation (для фронтендеров)
Вот полная документация нашего API на основе bash скрипта:
# Multiberry Backend API Documentation
## Base URL
## Authentication Endpoints
### 1. Register User
**POST** `/auth/register`
{ "bank_user_number": 6, "password": "testpass123" }
**Response (201 Created)**
{ "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...", "bank_user_id": "team275-6" }
---
### 2. Login
**POST** `/auth/login`
{ "bank_user_id": "team275-6", "password": "testpass123" }
**Response (200 OK)**
{ "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...", "bank_user_id": "team275-6" }
---
### 3. Get Current User Info
**GET** `/auth/me`
**Headers**
Authorization: Bearer {token}
**Response (200 OK)**
{ "user_id": 1, "bank_user_id": "team275-6" }
---
## Consent Management (Open Banking Permission)
### 4. Request Consent from Bank
**POST** `/consent/{bank}/{bank_user_id}`
**Parameters**
- `bank`: `vbank`, `abank`, или `sbank`
- `bank_user_id`: например `team275-6`
**Headers**
Authorization: Bearer {token}
**Response (201 Created)**
{ "consent_id": "consent-2c4ea1d99e5b", "expires_at": "2026-11-07T16:03:13.562301022Z", "status": "approved", "message": "Согласие одобрено автоматически" }
---
### 5. Get Consent Status
**GET** `/consent/{bank}/{bank_user_id}`
**Headers**
Authorization: Bearer {token}
**Response (200 OK)**
{ "consent_id": "consent-2c4ea1d99e5b", "bank": "vbank", "user_id": "team275-6" }
---
### 6. Revoke Consent
**DELETE** `/consent/{bank}/{bank_user_id}`
**Headers**
Authorization: Bearer {token}
**Response (200 OK)**
{ "status": "deleted" }
---
## Account Aggregation
### 7. Get All Accounts (from Bank via Multiberry)
**GET** `/accounts/{bank}/{bank_user_id}`
**Parameters**
- `bank`: `vbank`, `abank`, или `sbank`
**Headers**
Authorization: Bearer {token}
**Response (200 OK)**
{ "data": { "account": [ { "accountId": "acc-3848", "status": "Enabled", "currency": "RUB", "accountType": "Personal", "accountSubType": "Checking", "nickname": "Checking счет", "openingDate": "2024-10-30", "description": null, "account": [ { "schemeName": "RU.CBR.PAN", "identification": "4081781027508005359", "name": "Кредитов Кредит Кредитович (team275)" } ] } ] }, "links": { "self": "/accounts" }, "meta": { "totalPages": 1 } }
**Cached in DB** ✅ - следующий запрос вернёт из кэша
---
## Balance Retrieval
### 8. Get Account Balance (Cached)
**GET** `/balances/{bank}/{account_id}`
**Parameters**
- `bank`: `vbank`, `abank`, или `sbank`
- `account_id`: например `acc-3848`
**Headers**
Authorization: Bearer {token}
**Response (200 OK)**
{ "data": { "balance": [ { "accountId": "acc-3848", "type": "InterimAvailable", "dateTime": "2025-11-07T18:14:52.937793Z", "amount": { "amount": "115879.84", "currency": "RUB" }, "creditDebitIndicator": "Credit" }, { "accountId": "acc-3848", "type": "InterimBooked", "dateTime": "2025-11-07T18:14:52.937809Z", "amount": { "amount": "115879.84", "currency": "RUB" }, "creditDebitIndicator": "Credit" } ] } }
**Cached in DB** ✅
---
## Transaction History (для generative API)
### 9. Get Transactions (Paginated + Cached)
**GET** `/transactions/{bank}/{bank_user_id}/{account_id}?page=1&limit=6`
**Parameters**
- `bank`: `vbank`, `abank`, или `sbank`
- `bank_user_id`: например `team275-6`
- `account_id`: например `acc-3848`
- `page`: номер страницы (default: 1)
- `limit`: количество записей (default: 6)
**Headers**
Authorization: Bearer {token}
**Response (200 OK)**
{ "data": { "transaction": [ { "accountId": "acc-3848", "transactionId": "tx-team275-8-m0-1", "amount": { "amount": "80462.92", "currency": "RUB" }, "creditDebitIndicator": "Credit", "status": "Booked", "bookingDateTime": "2025-10-28T17:59:45.080562Z", "valueDateTime": "2025-10-28T17:59:45.080562Z", "transactionInformation": "💼 Зарплата", "bankTransactionCode": { "code": "ReceivedCreditTransfer" } } ] }, "links": { "self": "/accounts/acc-3848/transactions?page=1&limit=6", "next": "/accounts/acc-3848/transactions?page=2&limit=6" }, "meta": { "totalPages": 12, "totalRecords": 67, "currentPage": 1, "pageSize": 6 } }
**Cached in DB** ✅ - 67 транзакций сохранены!
---
## Error Handling
All endpoints return errors in format:
{ "error": "Description", "details": "Additional info if available" }
---
## Database Schema (что сохраняется)
-- Users: register/login users (id, bank_user_id, password_hash, created_at)
-- Consents: permissions from banks user_consents (user_id, bank_code, consent_id, status, expires_at)
-- Accounts: aggregated from all banks accounts (account_id, user_id, bank_code, nickname, currency, status, etc)
-- Balances: current balances (cached) balances (account_id, balance_type, amount, currency, date_time)
-- Transactions: all transactions (cached) transactions (transaction_id, account_id, bank_code, amount, currency, etc)
---
## Frontend Integration (Dioxus)
// Example: Get accounts for multi-bank view let accounts = fetch_accounts("vbank", "team275-6", token).await; for account in accounts.data.account { println!("{}: {}", account.nickname, account.currency); }
// Get transactions for chart/visualization let txs = fetch_transactions("vbank", "team275-6", "acc-3848", 1, 6, token).await; for tx in txs.data.transaction { // Pass to image generation API generate_transaction_chart(&tx).await; }
---
## Multi-Bank Usage
Get accounts from VBank
GET /accounts/vbank/team275-6
Get accounts from ABank (same user, different bank)
GET /accounts/abank/team275-6
Get accounts from SBank
GET /accounts/sbank/team275-6
All cached! Switch between banks instantly ⚡
Для Image Generation API
Структура данных транзакции для передачи в generative API:
{
"transaction": {
"accountId": "acc-3848",
"transactionId": "tx-team275-8-m0-1",
"amount": "80462.92",
"currency": "RUB",
"creditDebitIndicator": "Credit",
"transactionInformation": "💼 Зарплата",
"bookingDateTime": "2025-10-28T17:59:45.080562Z",
"status": "Booked"
},
"prompt_template": "Transaction: {transactionInformation} for {amount} {currency} - {creditDebitIndicator}"
}