(feat) all transactions

This commit is contained in:
Rorik Star Platinum 2025-11-07 22:02:03 +03:00
parent aeb9514aa3
commit eef798df5f
4 changed files with 533 additions and 1 deletions

View file

@ -0,0 +1,66 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT \n transaction_id,\n account_id,\n bank_code,\n amount,\n currency,\n credit_debit_indicator,\n transaction_information,\n booking_date_time\n FROM transactions\n WHERE account_id IN (\n SELECT account_id FROM accounts WHERE user_id = $1\n )\n ORDER BY booking_date_time DESC\n LIMIT $2 OFFSET $3\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "transaction_id",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "account_id",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "bank_code",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "amount",
"type_info": "Text"
},
{
"ordinal": 4,
"name": "currency",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "credit_debit_indicator",
"type_info": "Varchar"
},
{
"ordinal": 6,
"name": "transaction_information",
"type_info": "Text"
},
{
"ordinal": 7,
"name": "booking_date_time",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text",
"Int8",
"Int8"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
true,
false
]
},
"hash": "f95bb0ebb7f721265b971a743f09c6f2b0491616b91932b94b0ba8f22ba91d15"
}

381
README.md
View file

@ -149,6 +149,385 @@ just db-logs # Логи БД
docker ps # Список запущенных контейнеров 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"
}
```
--- ---
**Happy coding!** 🚀 ### 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}"
}
```
***

View file

@ -26,6 +26,9 @@ pub fn router(app_state: AppState) -> Router {
.route("/api/transactions/{bank}/{user_id}/{account_id}", .route("/api/transactions/{bank}/{user_id}/{account_id}",
get(handlers::get_transactions_handler) get(handlers::get_transactions_handler)
) )
.route("/api/transactions/{bank_user_id}",
get(handlers::get_all_transactions_handler) // ← Новый эндпоинт
)
.route("/api/balances/{bank}/{account_id}", .route("/api/balances/{bank}/{account_id}",
get(handlers::get_balances_handler) get(handlers::get_balances_handler)
) )

View file

@ -212,6 +212,90 @@ pub async fn get_transactions_handler(
Ok(Json(serde_json::to_value(transactions_response).unwrap())) Ok(Json(serde_json::to_value(transactions_response).unwrap()))
} }
pub async fn get_all_transactions_handler(
State(state): State<AppState>,
Path(bank_user_id): Path<String>,
Query(params): Query<TransactionQuery>, // ← Добавили query params
) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> {
let page = params.page.unwrap_or(1);
let limit = params.limit.unwrap_or(20); // Default: 20 на странице для ленты
info!("📊 Fetching ALL transactions page {} (limit {})", page, limit);
// Validate
if limit > 100 {
return Err((
StatusCode::BAD_REQUEST,
Json(json!({ "error": "limit max 100" }))
));
}
let offset = (page - 1) * limit;
// Get total count
let total_count: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM transactions WHERE account_id IN (
SELECT account_id FROM accounts WHERE user_id = $1
)"
)
.bind(&bank_user_id)
.fetch_one(&state.db_pool)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({ "error": e.to_string() }))))?;
// Get paginated transactions
let transactions = sqlx::query!(
r#"
SELECT
transaction_id,
account_id,
bank_code,
amount,
currency,
credit_debit_indicator,
transaction_information,
booking_date_time
FROM transactions
WHERE account_id IN (
SELECT account_id FROM accounts WHERE user_id = $1
)
ORDER BY booking_date_time DESC
LIMIT $2 OFFSET $3
"#,
bank_user_id,
limit as i64,
offset as i64
)
.fetch_all(&state.db_pool)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({ "error": e.to_string() }))))?;
let total_pages = (total_count as f64 / limit as f64).ceil() as u32;
Ok(Json(json!({
"data": {
"transactions": transactions.into_iter().map(|tx| json!({
"transaction_id": tx.transaction_id,
"account_id": tx.account_id,
"bank_code": tx.bank_code,
"amount": tx.amount,
"currency": tx.currency,
"credit_debit_indicator": tx.credit_debit_indicator,
"transaction_information": tx.transaction_information,
"booking_date_time": tx.booking_date_time,
})).collect::<Vec<_>>()
},
"meta": {
"current_page": page,
"page_size": limit,
"total_pages": total_pages,
"total_records": total_count,
"has_next": page < total_pages,
"has_prev": page > 1,
}
})))
}
pub async fn delete_consent_handler( pub async fn delete_consent_handler(