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>
104 lines
3.2 KiB
Rust
104 lines
3.2 KiB
Rust
//! Ortam değişkenlerinden uygulama ayarlarını okur.
|
||
|
||
use std::{env, path::PathBuf};
|
||
|
||
#[derive(Debug, Clone)]
|
||
pub struct Config {
|
||
pub mongodb_uri: String,
|
||
pub jwt_secret: String,
|
||
pub app_url: String,
|
||
pub port: u16,
|
||
pub registration: RegistrationMode,
|
||
/// Derlenmiş frontend klasörü (`STATIC_DIR`). Tanımlıysa Axum arayüzü de sunar
|
||
/// (canlıda tek servis). Geliştirmede boş: arayüzü Vite sunar.
|
||
pub static_dir: Option<PathBuf>,
|
||
/// Otomatik kur yenileme aralığı (dakika, `RATES_REFRESH_MINUTES`, varsayılan 60); 0 → kapalı
|
||
pub rates_refresh_minutes: u64,
|
||
}
|
||
|
||
/// Yeni hane kaydı politikası (`REGISTRATION` ortam değişkeni).
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
pub enum RegistrationMode {
|
||
/// Varsayılan. Sistemde hiç hane yokken ilk kayıt yeni hane kurar;
|
||
/// sonrasında yalnızca davet koduyla katılınır.
|
||
InviteOnly,
|
||
/// Herkes yeni hane kurabilir.
|
||
Open,
|
||
}
|
||
|
||
#[derive(Debug, thiserror::Error)]
|
||
pub enum ConfigError {
|
||
#[error("{0} ortam değişkeni tanımlı değil (.env dosyasını kontrol edin)")]
|
||
Missing(&'static str),
|
||
#[error("{0} geçersiz: {1}")]
|
||
Invalid(&'static str, &'static str),
|
||
}
|
||
|
||
impl Config {
|
||
pub fn from_env() -> Result<Self, ConfigError> {
|
||
let mongodb_uri = required("MONGODB_URI")?;
|
||
|
||
let jwt_secret = required("JWT_SECRET")?;
|
||
if jwt_secret.len() < 32 {
|
||
return Err(ConfigError::Invalid(
|
||
"JWT_SECRET",
|
||
"en az 32 karakter olmalı (openssl rand -hex 32)",
|
||
));
|
||
}
|
||
|
||
let app_url = env::var("APP_URL").unwrap_or_else(|_| "http://localhost:5173".into());
|
||
|
||
let registration = match env::var("REGISTRATION").as_deref() {
|
||
Err(_) | Ok("") | Ok("invite_only") => RegistrationMode::InviteOnly,
|
||
Ok("open") => RegistrationMode::Open,
|
||
Ok(_) => {
|
||
return Err(ConfigError::Invalid(
|
||
"REGISTRATION",
|
||
"\"invite_only\" veya \"open\" olmalı",
|
||
));
|
||
}
|
||
};
|
||
|
||
let static_dir = env::var("STATIC_DIR")
|
||
.ok()
|
||
.filter(|v| !v.trim().is_empty())
|
||
.map(PathBuf::from);
|
||
|
||
let port = match env::var("PORT") {
|
||
Ok(p) => p
|
||
.parse()
|
||
.map_err(|_| ConfigError::Invalid("PORT", "sayı olmalı"))?,
|
||
Err(_) => 8080,
|
||
};
|
||
|
||
let rates_refresh_minutes = match env::var("RATES_REFRESH_MINUTES") {
|
||
Ok(m) if !m.trim().is_empty() => m.trim().parse().map_err(|_| {
|
||
ConfigError::Invalid("RATES_REFRESH_MINUTES", "dakika (sayı) olmalı")
|
||
})?,
|
||
_ => 60,
|
||
};
|
||
|
||
Ok(Self {
|
||
mongodb_uri,
|
||
jwt_secret,
|
||
app_url,
|
||
port,
|
||
registration,
|
||
static_dir,
|
||
rates_refresh_minutes,
|
||
})
|
||
}
|
||
|
||
/// Cookie'lerde `Secure` bayrağı yalnızca HTTPS'te açılır.
|
||
pub fn is_https(&self) -> bool {
|
||
self.app_url.starts_with("https://")
|
||
}
|
||
}
|
||
|
||
fn required(key: &'static str) -> Result<String, ConfigError> {
|
||
env::var(key)
|
||
.ok()
|
||
.filter(|v| !v.trim().is_empty())
|
||
.ok_or(ConfigError::Missing(key))
|
||
}
|