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