(feat) transactions
This commit is contained in:
parent
35adb141ab
commit
779ae4d498
8 changed files with 214 additions and 5 deletions
14
.sqlx/query-2aeca288c85e977c8515a4231fddc9944bac2071c8ffa37973f1d4bd723f224e.json
generated
Normal file
14
.sqlx/query-2aeca288c85e977c8515a4231fddc9944bac2071c8ffa37973f1d4bd723f224e.json
generated
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM user_consents WHERE consent_id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2aeca288c85e977c8515a4231fddc9944bac2071c8ffa37973f1d4bd723f224e"
|
||||
}
|
||||
3
justfile
3
justfile
|
|
@ -36,6 +36,9 @@ db-reset:
|
|||
@docker compose down -v
|
||||
@{{sops_run}} 'docker compose up -d'
|
||||
|
||||
psql-exec:
|
||||
@{{sops_run}} 'psql $DATABASE_URL'
|
||||
|
||||
stop:
|
||||
@echo "🛑 Stopping all services..."
|
||||
@docker compose down
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// src/api/accounts.rs
|
||||
// Account data retrieval
|
||||
|
||||
use super::{client::{BankClient, BankingError}, models::{ApiResponse, AccountData}};
|
||||
use super::{client::{BankClient, BankingError}, models::{ApiResponse, AccountData, TransactionData}};
|
||||
|
||||
impl BankClient {
|
||||
pub async fn get_accounts(
|
||||
|
|
@ -28,4 +28,5 @@ impl BankClient {
|
|||
}),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,4 +62,32 @@ impl BankClient {
|
|||
consent
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn delete_consent(&self, consent_id: &str) -> Result<(), BankingError> {
|
||||
info!("🗑️ Deleting consent: {}", consent_id);
|
||||
|
||||
let token = self.get_token().await?;
|
||||
|
||||
let response = self.http_client
|
||||
.delete(self.base_url.join(&format!("/account-consents/{}", consent_id))?)
|
||||
.bearer_auth(token)
|
||||
.header("x-fapi-interaction-id", format!("team275-{}", chrono::Utc::now().timestamp()))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
match response.status().as_u16() {
|
||||
204 => {
|
||||
info!("✅ Consent deleted successfully");
|
||||
Ok(())
|
||||
},
|
||||
status => {
|
||||
error!("❌ Failed to delete consent: {}", status);
|
||||
Err(BankingError::ApiError {
|
||||
status,
|
||||
body: response.text().await.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
use sqlx::PgPool;
|
||||
use chrono::{DateTime, Utc};
|
||||
use tracing::{info};
|
||||
|
||||
pub struct StoredConsent {
|
||||
pub user_id: String,
|
||||
|
|
@ -55,3 +56,20 @@ pub async fn get_valid_consent(
|
|||
|
||||
Ok(result.map(|r| r.consent_id))
|
||||
}
|
||||
|
||||
pub async fn delete_consent(
|
||||
pool: &PgPool,
|
||||
consent_id: &str,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
info!("🗑️ Deleting consent from DB: {}", consent_id);
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM user_consents WHERE consent_id = $1",
|
||||
consent_id
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
info!("✅ Consent deleted from DB");
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ pub fn router(app_state: AppState) -> Router {
|
|||
.route("/api/consent/{bank}/{user_id}",
|
||||
post(handlers::create_consent_handler)
|
||||
.get(handlers::get_consent_handler)
|
||||
.delete(handlers::delete_consent_handler)
|
||||
)
|
||||
.route("/api/accounts/{bank}/{user_id}",
|
||||
get(handlers::get_accounts_handler)
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ pub async fn create_consent_handler(
|
|||
StatusCode::CREATED,
|
||||
Json(json!({
|
||||
"consent_id": consent_response.consent_id,
|
||||
"expires_at": consent_response.created_at + &chrono::Duration::days(365).to_string(),
|
||||
"expires_at": expires_at,
|
||||
"status": consent_response.status,
|
||||
"message": consent_response.message,
|
||||
}))
|
||||
|
|
@ -134,12 +134,31 @@ pub async fn get_accounts_handler(
|
|||
|
||||
let client = state.banking_clients.get_client(bank);
|
||||
|
||||
client.get_accounts(&user_id, &consent_id)
|
||||
let accounts_response = client.get_accounts(&user_id, &consent_id)
|
||||
.await
|
||||
.map(|accounts| Json(serde_json::to_value(accounts).unwrap()))
|
||||
.map_err(map_banking_error)
|
||||
.map_err(map_banking_error)?;
|
||||
|
||||
// ✨ NEW: Save accounts to database
|
||||
for account in &accounts_response.data.account {
|
||||
let _ = db::accounts::store_account(
|
||||
&state.db_pool,
|
||||
&account.account_id,
|
||||
&user_id,
|
||||
bank.code(),
|
||||
account.status.as_deref().unwrap_or("unknown"),
|
||||
&account.currency,
|
||||
&account.account_type,
|
||||
account.account_sub_type.as_deref(),
|
||||
&account.nickname,
|
||||
account.description.as_deref(),
|
||||
account.opening_date,
|
||||
).await;
|
||||
}
|
||||
|
||||
Ok(Json(serde_json::to_value(accounts_response).unwrap()))
|
||||
}
|
||||
|
||||
|
||||
// --- Transaction Management ---
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
@ -172,6 +191,35 @@ pub async fn get_transactions_handler(
|
|||
.map_err(map_banking_error)
|
||||
}
|
||||
|
||||
pub async fn delete_consent_handler(
|
||||
State(state): State<AppState>,
|
||||
Path((bank_code, user_id)): Path<(String, String)>,
|
||||
) -> impl IntoResponse {
|
||||
let bank = bank_code.parse::<Bank>()
|
||||
.map_err(|_| (StatusCode::BAD_REQUEST, Json(json!({ "error": "Invalid bank code" }))))?;
|
||||
|
||||
let consent_id = db::consents::get_valid_consent(&state.db_pool, &user_id, bank.code())
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({ "error": e.to_string() }))))?
|
||||
.ok_or_else(|| (StatusCode::NOT_FOUND, Json(json!({ "error": "Consent not found" }))))?;
|
||||
|
||||
let client = state.banking_clients.get_client(bank);
|
||||
|
||||
// Delete from bank first
|
||||
client.delete_consent(&consent_id)
|
||||
.await
|
||||
.map_err(map_banking_error)?;
|
||||
|
||||
// Then delete from our DB
|
||||
db::consents::delete_consent(&state.db_pool, &consent_id)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({ "error": e.to_string() }))))?;
|
||||
|
||||
Ok::<_, (StatusCode, Json<serde_json::Value>)>((StatusCode::OK, Json(json!({ "status": "deleted" }))))
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- Error Mapping ---
|
||||
|
||||
fn map_banking_error(err: BankingError) -> (StatusCode, Json<serde_json::Value>) {
|
||||
|
|
|
|||
96
test_multiberry.sh
Normal file
96
test_multiberry.sh
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#!/bin/bash
|
||||
# Save as test_multiberry.sh
|
||||
|
||||
BASE_URL="http://localhost:3000/api"
|
||||
|
||||
echo "=========================================="
|
||||
echo "1️⃣ REGISTER USER"
|
||||
echo "=========================================="
|
||||
REGISTER=$(curl -s -X POST $BASE_URL/auth/register \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"bank_user_number": 8, "password": "testpass123"}')
|
||||
|
||||
echo "$REGISTER" | jq .
|
||||
TOKEN=$(echo "$REGISTER" | jq -r '.token')
|
||||
BANK_USER_ID=$(echo "$REGISTER" | jq -r '.bank_user_id')
|
||||
|
||||
echo "✅ Token: ${TOKEN:0:50}..."
|
||||
echo "✅ Bank User ID: $BANK_USER_ID"
|
||||
echo ""
|
||||
|
||||
echo "=========================================="
|
||||
echo "2️⃣ LOGIN (verify token works)"
|
||||
echo "=========================================="
|
||||
LOGIN=$(curl -s -X POST $BASE_URL/auth/login \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"bank_user_id\": \"$BANK_USER_ID\", \"password\": \"testpass123\"}")
|
||||
|
||||
echo "$LOGIN" | jq .
|
||||
echo ""
|
||||
|
||||
echo "=========================================="
|
||||
echo "3️⃣ GET ME (verify auth middleware)"
|
||||
echo "=========================================="
|
||||
curl -s http://localhost:3000/api/auth/me \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
echo ""
|
||||
|
||||
echo "=========================================="
|
||||
echo "4️⃣ REQUEST CONSENT from VBank"
|
||||
echo "=========================================="
|
||||
CONSENT=$(curl -s -X POST $BASE_URL/consent/vbank/$BANK_USER_ID \
|
||||
-H "Authorization: Bearer $TOKEN")
|
||||
|
||||
echo "$CONSENT" | jq .
|
||||
CONSENT_ID=$(echo "$CONSENT" | jq -r '.consent_id')
|
||||
echo "✅ Consent ID: $CONSENT_ID"
|
||||
echo ""
|
||||
|
||||
echo "=========================================="
|
||||
echo "5️⃣ GET ACCOUNTS (auto-saved to DB)"
|
||||
echo "=========================================="
|
||||
ACCOUNTS=$(curl -s $BASE_URL/accounts/vbank/$BANK_USER_ID \
|
||||
-H "Authorization: Bearer $TOKEN")
|
||||
|
||||
echo "$ACCOUNTS" | jq .
|
||||
ACCOUNT_ID=$(echo "$ACCOUNTS" | jq -r '.data.account[0].accountId')
|
||||
echo "✅ Account ID: $ACCOUNT_ID"
|
||||
echo ""
|
||||
|
||||
echo "=========================================="
|
||||
echo "6️⃣ GET BALANCES"
|
||||
echo "=========================================="
|
||||
curl -s $BASE_URL/balances/vbank/$BANK_USER_ID \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
echo ""
|
||||
|
||||
echo "=========================================="
|
||||
echo "7️⃣ GET TRANSACTIONS (page 1, limit 6)"
|
||||
echo "=========================================="
|
||||
curl -s "$BASE_URL/transactions/vbank/$BANK_USER_ID/$ACCOUNT_ID?page=1&limit=6" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
echo ""
|
||||
|
||||
echo "=========================================="
|
||||
echo "8️⃣ GET TRANSACTIONS (page 2, limit 6)"
|
||||
echo "=========================================="
|
||||
curl -s "$BASE_URL/transactions/vbank/$BANK_USER_ID/$ACCOUNT_ID?page=2&limit=6" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
echo ""
|
||||
|
||||
echo "=========================================="
|
||||
echo "9️⃣ DELETE CONSENT"
|
||||
echo "=========================================="
|
||||
curl -s -X DELETE $BASE_URL/consent/vbank/$BANK_USER_ID \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
echo ""
|
||||
|
||||
echo "=========================================="
|
||||
echo "🔟 VERIFY DB (from another terminal)"
|
||||
echo "=========================================="
|
||||
echo "just psql-exec"
|
||||
echo "SELECT * FROM users;"
|
||||
echo "SELECT * FROM user_consents;"
|
||||
echo "SELECT * FROM accounts;"
|
||||
echo "SELECT * FROM transactions;"
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue