use axum::{Router, response::IntoResponse};
use tower_http::services::ServeDir;
use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer, key_extractor::SmartIpKeyExtractor};
use axum_session::{SessionLayer, SessionStore};
use crate::app::Config;
use crate::session_manager::RustBasicSessionStore;
use crate::errors::ErrorController;
use tower_governor::GovernorError;
use std::net::SocketAddr;
use sea_orm::DatabaseConnection;
use std::sync::Arc;
use std::process::Command;
use std::time::Duration;
use tower_livereload::LiveReloadLayer;
#[derive(Clone)]
#[allow(dead_code)]
pub struct AppState {
pub db: DatabaseConnection,
pub config: Arc<Config>,
}
pub async fn start_server(
cfg: Config,
session_store: SessionStore<RustBasicSessionStore>,
static_files: ServeDir,
db: DatabaseConnection,
app_router: Router<AppState>
) {
kill_port_if_in_use(cfg.app_port);
unsafe {
std::env::set_var("TZ", &cfg.app_timezone);
}
let state = AppState {
db,
config: Arc::new(cfg.clone()),
};
let governor_conf = Arc::new(
GovernorConfigBuilder::default()
.key_extractor(SmartIpKeyExtractor)
.period(Duration::from_millis(1000 / cfg.app_limit_request))
.burst_size(cfg.app_limit_request as u32)
.finish()
.unwrap(),
);
let app = Router::new()
.merge(app_router)
.nest_service("/public", static_files)
.layer(GovernorLayer::new(governor_conf))
.layer(SessionLayer::new(session_store))
.fallback(ErrorController::not_found)
.with_state(state);
let app = if cfg.app_debug {
tracing::info!("🔄 Fitur Live Reload (Auto-refresh) diaktifkan.");
app.layer(LiveReloadLayer::new())
} else {
app
};
let addr_str = format!("{}:{}", cfg.app_host, cfg.app_port);
let addr: SocketAddr = addr_str.parse().expect("Alamat server tidak valid");
tracing::info!("{} berjalan di: http://{}", cfg.app_name, addr);
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>()).await.unwrap();
}
fn kill_port_if_in_use(port: u16) {
#[cfg(target_os = "macos")]
{
let output = Command::new("lsof")
.arg("-t")
.arg(format!("-i:{}", port))
.output();
if let Ok(out) = output {
let pid_str = String::from_utf8_lossy(&out.stdout).trim().to_string();
if !pid_str.is_empty() {
tracing::warn!("Port {} sedang digunakan oleh PID {}. Membunuh proses...", port, pid_str);
for pid in pid_str.split('\n') {
let _ = Command::new("kill")
.arg("-9")
.arg(pid)
.output();
}
}
}
}
#[cfg(target_os = "linux")]
{
let _ = Command::new("fuser")
.arg("-k")
.arg(format!("{}/tcp", port))
.output();
}
}
#[allow(dead_code)]
fn handle_governor_error(err: GovernorError) -> axum::response::Response {
match err {
GovernorError::TooManyRequests { wait_time, .. } => {
ErrorController::show(
429,
&format!("Terlalu banyak permintaan. Silakan tunggu {} detik lagi.", wait_time)
).into_response()
},
_ => ErrorController::show(500, "Terjadi kesalahan pada sistem pembatas request.").into_response(),
}
}