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