1use axum::{Router, response::IntoResponse};
2use tower_http::services::ServeDir;
3use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer, key_extractor::SmartIpKeyExtractor};
4use axum_session::{SessionLayer, SessionStore};
5use crate::app::Config;
6use crate::session_manager::RustBasicSessionStore;
7use crate::errors::ErrorController;
8use tower_governor::GovernorError;
9use std::net::SocketAddr;
10use sea_orm::DatabaseConnection;
11use std::sync::Arc;
12use std::process::Command;
13use std::time::Duration;
14use tower_livereload::LiveReloadLayer;
15
16#[derive(Clone)]
17#[allow(dead_code)]
18pub struct AppState {
19 pub db: DatabaseConnection,
20 pub config: Arc<Config>,
21}
22
23pub async fn start_server(
24 cfg: Config,
25 session_store: SessionStore<RustBasicSessionStore>,
26 static_files: ServeDir,
27 db: DatabaseConnection,
28 app_router: Router<AppState>
29) {
30 kill_port_if_in_use(cfg.app_port);
32
33 unsafe {
35 std::env::set_var("TZ", &cfg.app_timezone);
36 }
37
38 let state = AppState {
40 db,
41 config: Arc::new(cfg.clone()),
42 };
43
44 let governor_conf = Arc::new(
46 GovernorConfigBuilder::default()
47 .key_extractor(SmartIpKeyExtractor)
48 .period(Duration::from_millis(1000 / cfg.app_limit_request))
49 .burst_size(cfg.app_limit_request as u32)
50 .finish()
51 .unwrap(),
52 );
53
54 let app = Router::new()
56 .merge(app_router)
57 .nest_service("/public", static_files)
58 .layer(GovernorLayer::new(governor_conf))
59 .layer(SessionLayer::new(session_store))
60 .fallback(ErrorController::not_found)
61 .with_state(state);
62
63 let app = if cfg.app_debug {
65 tracing::info!("🔄 Fitur Live Reload (Auto-refresh) diaktifkan.");
66 app.layer(LiveReloadLayer::new())
67 } else {
68 app
69 };
70
71 let addr_str = format!("{}:{}", cfg.app_host, cfg.app_port);
73 let addr: SocketAddr = addr_str.parse().expect("Alamat server tidak valid");
74
75 tracing::info!("{} berjalan di: http://{}", cfg.app_name, addr);
76
77 let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
79 axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>()).await.unwrap();
80}
81
82fn kill_port_if_in_use(port: u16) {
84 #[cfg(target_os = "macos")]
85 {
86 let output = Command::new("lsof")
88 .arg("-t")
89 .arg(format!("-i:{}", port))
90 .output();
91
92 if let Ok(out) = output {
93 let pid_str = String::from_utf8_lossy(&out.stdout).trim().to_string();
94 if !pid_str.is_empty() {
95 tracing::warn!("Port {} sedang digunakan oleh PID {}. Membunuh proses...", port, pid_str);
96
97 for pid in pid_str.split('\n') {
99 let _ = Command::new("kill")
100 .arg("-9")
101 .arg(pid)
102 .output();
103 }
104 }
105 }
106 }
107
108 #[cfg(target_os = "linux")]
109 {
110 let _ = Command::new("fuser")
111 .arg("-k")
112 .arg(format!("{}/tcp", port))
113 .output();
114 }
115}
116
117#[allow(dead_code)]
119fn handle_governor_error(err: GovernorError) -> axum::response::Response {
120 match err {
121 GovernorError::TooManyRequests { wait_time, .. } => {
122 ErrorController::show(
123 429,
124 &format!("Terlalu banyak permintaan. Silakan tunggu {} detik lagi.", wait_time)
125 ).into_response()
126 },
127 _ => ErrorController::show(500, "Terjadi kesalahan pada sistem pembatas request.").into_response(),
128 }
129}