3f60efdc33
- TCMB (döviz alış kuru) ve xaus.com (altın) saatlik arka planda çekilir, gün başına saklanır - Kaynak erişilemezse son kur geçerli; altın için son USD kuru kullanılır - Elle kur girişi/silme; aynı gün elle girilen kur önceliklidir - GET /portfolio: güncel değer, kâr/zarar, toplamlar, son kur çekimi - RATES_REFRESH_MINUTES ayarı Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
2.9 KiB
Rust
95 lines
2.9 KiB
Rust
use std::{net::SocketAddr, process::ExitCode};
|
||
|
||
use kakeibo::{config::Config, db, routes, services, state::AppState};
|
||
use tracing_subscriber::EnvFilter;
|
||
|
||
#[tokio::main]
|
||
async fn main() -> ExitCode {
|
||
// .env dosyasını yükle (proje kökünde; üst klasörler de aranır). Yoksa sorun değil.
|
||
dotenvy::dotenv().ok();
|
||
|
||
tracing_subscriber::fmt()
|
||
.with_env_filter(
|
||
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("kakeibo=info")),
|
||
)
|
||
.init();
|
||
|
||
let config = match Config::from_env() {
|
||
Ok(c) => c,
|
||
Err(e) => {
|
||
tracing::error!("Ayar hatası: {e}");
|
||
return ExitCode::FAILURE;
|
||
}
|
||
};
|
||
|
||
let db = match db::connect(&config.mongodb_uri).await {
|
||
Ok(db) => {
|
||
tracing::info!("MongoDB bağlantısı kuruldu (veritabanı: {})", db.name());
|
||
db
|
||
}
|
||
Err(e) => {
|
||
tracing::error!(
|
||
"MongoDB'ye bağlanılamadı. MONGODB_URI doğru mu, veritabanı çalışıyor mu? \
|
||
(yerelde: npm run db:up)\nDetay: {e}"
|
||
);
|
||
return ExitCode::FAILURE;
|
||
}
|
||
};
|
||
|
||
if let Err(e) = db::prepare(&db).await {
|
||
tracing::error!("Veritabanı hazırlanamadı (migration/indeks): {e}");
|
||
return ExitCode::FAILURE;
|
||
}
|
||
|
||
// Kurlar arka planda (açılışta hemen, sonra aralıkla); hata uygulamayı durdurmaz
|
||
services::rates::spawn_refresher(db.clone(), config.rates_refresh_minutes);
|
||
|
||
let port = config.port;
|
||
let state = AppState::new(db, config);
|
||
let app = routes::app(state);
|
||
|
||
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
||
let listener = match tokio::net::TcpListener::bind(addr).await {
|
||
Ok(l) => l,
|
||
Err(e) => {
|
||
tracing::error!(
|
||
"{port} portu dinlenemedi (başka bir uygulama kullanıyor olabilir): {e}"
|
||
);
|
||
return ExitCode::FAILURE;
|
||
}
|
||
};
|
||
|
||
tracing::info!("Kakeibo API çalışıyor: http://localhost:{port}");
|
||
if let Err(e) = axum::serve(listener, app)
|
||
.with_graceful_shutdown(shutdown_signal())
|
||
.await
|
||
{
|
||
tracing::error!("Sunucu hatası: {e}");
|
||
return ExitCode::FAILURE;
|
||
}
|
||
ExitCode::SUCCESS
|
||
}
|
||
|
||
/// Docker/Dokploy durdururken (SIGTERM) veya Ctrl+C'de: yeni istek almayı bırak,
|
||
/// süren istekleri bitir, sonra kapan (yarım kalan yazma olmasın).
|
||
async fn shutdown_signal() {
|
||
let ctrl_c = async {
|
||
let _ = tokio::signal::ctrl_c().await;
|
||
};
|
||
#[cfg(unix)]
|
||
let terminate = async {
|
||
if let Ok(mut s) = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
|
||
{
|
||
s.recv().await;
|
||
}
|
||
};
|
||
#[cfg(not(unix))]
|
||
let terminate = std::future::pending::<()>();
|
||
|
||
tokio::select! {
|
||
_ = ctrl_c => {},
|
||
_ = terminate => {},
|
||
}
|
||
tracing::info!("Kapatma sinyali alındı, sunucu kapanıyor…");
|
||
}
|