use crate::core::config::load_rustls_config;
use crate::routes::Routes;
use actix_web::{App, HttpServer, web};
use actix_governor::{Governor, GovernorConfigBuilder};
use actix_cors::Cors;
use std::sync::{Arc, Once};
static INIT: Once = Once::new();
fn initialize_crypto_provider() {
INIT.call_once(|| {
let _ = rustls::crypto::CryptoProvider::install_default(rustls::crypto::ring::default_provider());
});
}
pub struct Api {
cert_path: String,
key_path: String,
addr: String,
port: u16,
rate_limit: (u64, u32),
custom_routes: Option<Arc<dyn Fn(&mut web::ServiceConfig) + Send + Sync>>,
custom_cors: Arc<dyn Fn() -> Cors + Send + Sync>,
user_db: bool,
login_route: String,
register_route: String,
}
impl Api {
pub fn new() -> Self {
initialize_crypto_provider(); Self {
cert_path: "certs/cert.pem".into(),
key_path: "certs/key.pem".into(),
addr: "127.0.0.1".into(),
port: 8443,
rate_limit: (3, 20),
custom_routes: None,
custom_cors: Arc::new(|| Cors::default()),
user_db: false,
login_route: "/login".into(),
register_route: "/register".into(),
}
}
pub fn certs(mut self, cert: &str, key: &str) -> Self {
self.cert_path = cert.into();
self.key_path = key.into();
self
}
pub fn rate_limit(mut self, per_second: u64, burst_size: u32) -> Self {
self.rate_limit = (per_second, burst_size);
self
}
pub fn bind(mut self, addr: &str, port: u16) -> Self {
self.addr = addr.into();
self.port = port;
self
}
pub fn configure_routes(mut self, routes: Routes) -> Self {
self.custom_routes = Some(Arc::new(move |cfg| routes.configure(cfg)));
self
}
pub fn configure_cors<F>(mut self, cors_config: F) -> Self
where
F: Fn() -> Cors + Send + Sync + 'static,
{
self.custom_cors = Arc::new(cors_config);
self
}
pub fn enable_user_db(self) -> Self {
self.enable_user_db_with_routes("/login", "/register")
}
pub fn enable_user_db_with_routes(mut self, login_route: &str, register_route: &str) -> Self {
self.user_db = true;
self.login_route = login_route.into();
self.register_route = register_route.into();
self
}
pub fn start(self) {
let rt = actix_web::rt::System::new();
if let Err(e) = rt.block_on(async {
println!("INFO: Starting API server...");
dotenv::dotenv().ok();
let pool = if self.user_db {
Some(crate::core::db::init_db().await.expect("Failed to init DB"))
} else {
None
};
let tls_config = load_rustls_config(&self.cert_path, &self.key_path).expect("TLS failed");
let governor_config = GovernorConfigBuilder::default()
.per_second(self.rate_limit.0)
.burst_size(self.rate_limit.1)
.finish()
.unwrap();
let cors_config = self.custom_cors.clone();
let bind_addr = format!("{}:{}", self.addr, self.port);
println!("INFO: Server binding to {}", bind_addr);
HttpServer::new(move || {
let cors = (cors_config)();
let mut app = App::new()
.wrap(cors)
.wrap(Governor::new(&governor_config));
if let Some(pool) = pool.clone() {
app = app.app_data(web::Data::new(pool));
app = app.configure(|cfg| {
crate::core::auth_routes::configure_auth_routes(
cfg,
&self.login_route,
&self.register_route
);
});
}
if let Some(custom_routes) = &self.custom_routes {
app = app.configure(|cfg| custom_routes(cfg));
}
app
})
.bind_rustls_0_23((self.addr.to_string(), self.port), tls_config)?
.run()
.await
}) {
println!("ERROR: Failed to start API server: {:?}", e);
}
}
pub fn get_cert_path(&self) -> &str { &self.cert_path }
pub fn get_key_path(&self) -> &str { &self.key_path }
pub fn get_addr(&self) -> &str { &self.addr }
pub fn get_port(&self) -> u16 { self.port }
pub fn get_rate_limit(&self) -> (u64, u32) { self.rate_limit }
pub fn get_bind_addr(&self) -> String { format!("{}:{}", self.addr, self.port) }
pub fn get_rate_limit_per_second(&self) -> u64 { self.rate_limit.0 }
pub fn get_rate_limit_burst_size(&self) -> u32 { self.rate_limit.1 }
pub fn get_custom_routes(&self) -> Option<&Arc<dyn Fn(&mut web::ServiceConfig) + Send + Sync>> { self.custom_routes.as_ref() }
}