multiberry-backend/README.md

533 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Multiberry Backend
Веб-приложение для хакатона ВТБ. Backend на Rust + Axum, БД — PostgreSQL.
## 🚀 Быстрый старт (macOS)
### 1. Установка зависимостей
при условии что docker, sops, age уже установлены
just - это Makefile 2.0
```zsh
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. Клонируй репозиторий
```bash
git clone ssh://git@gitverse.ru:2222/rorikstr/multiberry-backend.git
cd multiberry-backend
```
### 3. Запусти проект
Короч. Чтобы не запускать бесконечное число раз sops exec-env для вскрытия secrets.yaml
я придумал выполнять проект на Makefile 2.0: justfile
```bash
# Посмотри все доступные команды
just
# Запусти базу данных
just db-up
# В новом терминале запусти бэкенд
just run
# Проверь, что всё работает
just health
```
**Ожидаемый результат:**
```json
{"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 скрипта:
```markdown
# 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:
```json
{
"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}"
}
```
***