multiberry-backend/README.md

11 KiB
Raw Permalink Blame History

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 разработки

  1. Создай фича-ветку:

    git checkout -b feature/my-feature
    
  2. Запусти проект:

    just db-up
    just run
    
  3. Внеси изменения в src/main.rs

  4. Тесты:

    just test
    
  5. 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

http://localhost:3000/api


## 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}"
}