pub mod db;
pub mod entities;
pub mod gateway;
pub mod migration;
pub mod webhook;
use axum::{
http::{header, HeaderValue, Method},
routing::post,
Router,
};
use sea_orm::DatabaseConnection;
use tokio::sync::watch;
use tower_http::cors::CorsLayer;
use tracing::info;
use webhook::agent_webhook;
pub use gateway::WebhookGateway;
#[derive(Clone)]
pub struct WebAppState {
pub db: DatabaseConnection,
pub shutdown_tx: watch::Sender<bool>,
}
pub async fn serve(db: DatabaseConnection, port: u16) -> anyhow::Result<()> {
serve_with_extra(db, port, None).await
}
pub async fn serve_with_extra(
db: DatabaseConnection,
port: u16,
extra: Option<axum::Router>,
) -> anyhow::Result<()> {
let (shutdown_tx, _) = watch::channel(false);
let state = WebAppState {
db,
shutdown_tx: shutdown_tx.clone(),
};
let mut app = build_router_with_state(state);
if let Some(extra_router) = extra {
app = app.merge(extra_router);
}
let addr = format!("0.0.0.0:{}", port);
info!("Webhook server listening on {}", addr);
let listener = tokio::net::TcpListener::bind(&addr).await?;
axum::serve(listener, app)
.with_graceful_shutdown(async move {
shutdown_signal().await;
info!("Shutdown signal received — stopping webhook server");
let _ = shutdown_tx.send(true);
})
.await?;
Ok(())
}
pub fn build_router(db: DatabaseConnection) -> Router {
let (shutdown_tx, _) = watch::channel(false);
let state = WebAppState { db, shutdown_tx };
build_router_with_state(state)
}
fn build_router_with_state(state: WebAppState) -> Router {
let cors = CorsLayer::new()
.allow_origin("http://localhost:5173".parse::<HeaderValue>().unwrap())
.allow_methods([
Method::GET,
Method::POST,
Method::PUT,
Method::DELETE,
Method::OPTIONS,
])
.allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE]);
Router::new()
.route("/webhook", post(agent_webhook))
.layer(cors)
.with_state(state)
}
async fn shutdown_signal() {
let ctrl_c = async {
tokio::signal::ctrl_c()
.await
.expect("Failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("Failed to install SIGTERM handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {}
_ = terminate => {}
}
}