use std::net::SocketAddr;
use anyhow::Result;
use tokio::net::TcpListener;
use tokio::signal;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
use sevensense_api::{AppBuilder, Config};
#[tokio::main]
async fn main() -> Result<()> {
init_tracing();
let config = Config::from_env()?;
tracing::info!(
host = %config.host,
port = %config.port,
"Starting 7sense API server"
);
let app = AppBuilder::new(config.clone()).build().await?;
let addr: SocketAddr = format!("{}:{}", config.host, config.port).parse()?;
let listener = TcpListener::bind(addr).await?;
tracing::info!(
address = %addr,
"7sense API server listening"
);
print_banner(&config);
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await?;
tracing::info!("7sense API server shut down gracefully");
Ok(())
}
fn init_tracing() {
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("sevensense_api=info,tower_http=info,axum=info"));
tracing_subscriber::registry()
.with(env_filter)
.with(
tracing_subscriber::fmt::layer()
.with_target(true)
.with_thread_ids(false)
.with_file(false)
.with_line_number(false),
)
.init();
}
fn print_banner(config: &Config) {
let base_url = format!("http://{}:{}", config.host, config.port);
println!();
println!("========================================");
println!(" 7sense Bioacoustic API");
println!("========================================");
println!();
println!(" REST API: {base_url}/api/v1/");
println!(" GraphQL: {base_url}/graphql");
println!(" WebSocket: ws://{}:{}/ws/", config.host, config.port);
println!(" Swagger UI: {base_url}/docs/swagger");
println!(" OpenAPI: {base_url}/docs/openapi.json");
println!();
println!(" Health: {base_url}/api/v1/health");
println!();
if config.api_key.is_some() {
println!(" Auth: API key required (Bearer token)");
} else {
println!(" Auth: No authentication (development mode)");
}
if config.enable_playground {
println!(" Playground: Enabled");
}
println!();
println!(" Rate limit: {} req/sec", config.rate_limit_rps);
println!(
" Max upload: {} MB",
config.max_upload_size / 1024 / 1024
);
println!();
println!("========================================");
println!();
}
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("Failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("Failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
() = ctrl_c => {
tracing::info!("Received Ctrl+C, initiating shutdown");
}
() = terminate => {
tracing::info!("Received SIGTERM, initiating shutdown");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_parsing() {
let config = Config::default();
assert_eq!(config.host, "0.0.0.0");
assert_eq!(config.port, 8080);
}
}