From 8a03bdee96bf3f1f8e523fbbcff80e47ae1b2fc5 Mon Sep 17 00:00:00 2001 From: Rorik Star Platinum Date: Wed, 5 Nov 2025 00:10:18 +0300 Subject: [PATCH] (refactor) more clean architecture --- Cargo.lock | 165 ++++++++++++++++++++++++++++++++++- Cargo.toml | 2 +- flake.nix | 4 +- src/db.rs | 28 ++++++ src/main.rs | 68 +++++---------- src/route.rs | 24 +++++ src/route/handlers.rs | 40 +++++++++ tests/banking_integration.rs | 0 8 files changed, 278 insertions(+), 53 deletions(-) create mode 100644 src/db.rs create mode 100644 src/route.rs create mode 100644 src/route/handlers.rs create mode 100644 tests/banking_integration.rs diff --git a/Cargo.lock b/Cargo.lock index daf0f30..af3ff73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,30 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "axum" version = "0.8.6" @@ -127,6 +151,26 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + [[package]] name = "bitflags" version = "2.10.0" @@ -170,9 +214,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -193,6 +248,26 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "colored" version = "3.0.0" @@ -331,6 +406,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "either" version = "1.15.0" @@ -446,6 +527,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures-channel" version = "0.3.31" @@ -551,6 +638,12 @@ dependencies = [ "wasip2", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "h2" version = "0.4.12" @@ -914,12 +1007,31 @@ dependencies = [ "serde", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.82" @@ -936,6 +1048,7 @@ version = "10.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d119c6924272d16f0ab9ce41f7aa0bfef9340c00b0bb7ca3dd3b263d4a9150b" dependencies = [ + "aws-lc-rs", "base64", "getrandom 0.2.16", "js-sys", @@ -961,6 +1074,16 @@ version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + [[package]] name = "libm" version = "0.2.15" @@ -1043,6 +1166,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "mio" version = "1.1.0" @@ -1118,6 +1247,16 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1356,6 +1495,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -1527,7 +1676,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.16", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -1551,6 +1700,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" version = "1.1.2" @@ -1595,7 +1750,7 @@ checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -2410,6 +2565,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index f133d3f..06c98e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ serde = { version = "1", features = ["derive"] } serde_json = "1.0" -jsonwebtoken = "10.1" +jsonwebtoken = { version = "10.1", features = ["aws_lc_rs"] } base64 = "0.22" chrono = { version = "0.4", features = ["serde"] } diff --git a/flake.nix b/flake.nix index 10c0b2a..2f41963 100644 --- a/flake.nix +++ b/flake.nix @@ -15,8 +15,8 @@ sqlx-cli openssl - pkg-config # Помогает Cargo находить системные библиотеки - zlib # Часто требуется для компиляции OpenSSL + pkg-config + zlib ]; shellHook = '' export OPENSSL_DIR="${pkgs.openssl.dev}" diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..ccae23a --- /dev/null +++ b/src/db.rs @@ -0,0 +1,28 @@ +// src/db.rs +// Почему это здесь? +// - Это всё, связанное с инициализацией и конфигурацией базы данных +// - Здесь создаётся connection pool, который переиспользуется во всём приложении + +use sqlx::postgres::PgPoolOptions; +use sqlx::PgPool; +use std::env; + +/// Инициализирует PgPool (connection pool для PostgreSQL) +/// +/// Connection pool — это набор переиспользуемых соединений к БД. +/// Вместо того, чтобы открывать новое соединение для каждого запроса, +/// мы берём готовое соединение из пула. +/// +/// Это **критически важно** для производительности: +/// - Открытие соединения — медленная операция +/// - Connection pool решает эту проблему +pub async fn init_pool() -> PgPool { + let database_url = env::var("DATABASE_URL") + .expect("DATABASE_URL must be set"); + + PgPoolOptions::new() + .max_connections(5) // Максимум 5 одновременных соединений + .connect(&database_url) + .await + .expect("Failed to create Postgres connection pool") +} diff --git a/src/main.rs b/src/main.rs index ead0fe1..898ac0e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,64 +1,36 @@ -// Подключаем модули напрямую без mod.rs! -mod banking { - pub mod client; - pub mod models; - pub mod error; -} +// src/main.rs +mod route; // Объявляем модуль route (это либо route.rs, либо route/mod.rs) +mod db; // Объявляем модуль для работы с БД -mod services { - pub mod account_service; - pub mod consent_service; -} - -mod handlers { - pub mod health; - pub mod accounts; -} - -mod config; -mod db; -mod error; - -use axum::{ - routing::get, - Router, -}; use std::net::SocketAddr; +use sqlx::PgPool; + +/// Общее состояние приложения, передаётся во все handlers +/// Это паттерн Axum: State используется в extract'ах +#[derive(Clone)] +pub struct AppState { + pub db_pool: PgPool, +} #[tokio::main] async fn main() { + // 1. Загружаем переменные окружения из .env или secrets (через sops) dotenvy::dotenv().ok(); + // 2. Инициализируем базу данных let db_pool = db::init_pool().await; - - let banking_clients = banking::client::init_all_banks().await; + println!("✅ Database connection pool created successfully."); - let app_state = AppState { - db_pool, - banking_clients, - }; + // 3. Создаём общее состояние приложения + let app_state = AppState { db_pool }; - let app = Router::new() - .route("/api/health", get(handlers::health::health_handler)) - .route("/api/accounts/aggregated", get(handlers::accounts::get_aggregated_accounts)) - .with_state(app_state); + // 4. Конфигурируем роуты (все роуты централизованы в route::router()) + let app = route::router(app_state); + // 5. Запускаем сервер let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); println!("🚀 Server listening on {}", addr); - + let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); axum::serve(listener, app).await.unwrap(); } - -#[derive(Clone)] -pub struct AppState { - pub db_pool: sqlx::PgPool, - pub banking_clients: BankingClients, -} - -#[derive(Clone)] -pub struct BankingClients { - pub vbank: banking::client::BankClient, - pub abank: banking::client::BankClient, - pub sbank: banking::client::BankClient, -} diff --git a/src/route.rs b/src/route.rs new file mode 100644 index 0000000..b881ee9 --- /dev/null +++ b/src/route.rs @@ -0,0 +1,24 @@ +// src/route.rs +// Почему это здесь? +// - Это центр управления всеми HTTP маршрутами (роутами) +// - Здесь объявляются все подмодули (handlers, будущие сервисы и т.д.) +// - Здесь собирается финальный Router для Axum + +pub mod handlers; // Объявляем подмодуль handlers + +use axum::{routing::get, Router}; +use crate::AppState; + +/// Создаёт и возвращает Router со всеми сконфигурированными роутами +/// Функция принимает AppState и передаёт его всем handlers'ам +pub fn router(app_state: AppState) -> Router { + Router::new() + // GET /api/health — health-check эндпоинт + .route("/api/health", get(handlers::health_handler)) + + // Сюда добавим новые роуты по мере разработки: + // .route("/api/accounts", get(handlers::accounts::get_accounts)) + // .route("/api/payments", post(handlers::payments::create_payment)) + + .with_state(app_state) +} diff --git a/src/route/handlers.rs b/src/route/handlers.rs new file mode 100644 index 0000000..baaac4f --- /dev/null +++ b/src/route/handlers.rs @@ -0,0 +1,40 @@ +// src/route/handlers.rs +// Почему это здесь? +// - Это всё, что обрабатывает HTTP запросы +// - Каждый handler'а — это async функция, которая обрабатывает запрос и возвращает ответ + +use axum::{ + extract::State, + http::StatusCode, + response::IntoResponse, + Json, +}; +use serde_json::json; +use crate::AppState; + +/// Health-check handler +/// +/// Что он делает: +/// 1. Принимает AppState через extract::State (это параметр, который Axum инжектирует) +/// 2. Пытается выполнить простой SELECT 1 в БД (проверка подключения) +/// 3. Возвращает 200 OK если всё хорошо, или 500 если БД недоступна +pub async fn health_handler( + State(state): State, +) -> impl IntoResponse { + // Пытаемся выполнить простой запрос к БД + let result = sqlx::query("SELECT 1") + .execute(&state.db_pool) + .await; + + // Обрабатываем результат + match result { + Ok(_) => ( + StatusCode::OK, + Json(json!({ "status": "Database connection is successful." })), + ).into_response(), + Err(e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "status": format!("Database connection failed: {}", e) })), + ).into_response(), + } +} diff --git a/tests/banking_integration.rs b/tests/banking_integration.rs new file mode 100644 index 0000000..e69de29