robson_gateway_webhook/
lib.rs1pub mod db;
2pub mod entities;
3pub mod gateway;
4pub mod migration;
5pub mod webhook;
6
7use axum::{
8 http::{header, HeaderValue, Method},
9 routing::post,
10 Router,
11};
12use sea_orm::DatabaseConnection;
13use tokio::sync::watch;
14use tower_http::cors::CorsLayer;
15use tracing::info;
16use webhook::agent_webhook;
17
18pub use gateway::WebhookGateway;
19
20#[derive(Clone)]
21pub struct WebAppState {
22 pub db: DatabaseConnection,
23 pub shutdown_tx: watch::Sender<bool>,
24}
25
26pub async fn serve(db: DatabaseConnection, port: u16) -> anyhow::Result<()> {
27 serve_with_extra(db, port, None).await
28}
29
30pub async fn serve_with_extra(
31 db: DatabaseConnection,
32 port: u16,
33 extra: Option<axum::Router>,
34) -> anyhow::Result<()> {
35 let (shutdown_tx, _) = watch::channel(false);
36 let state = WebAppState {
37 db,
38 shutdown_tx: shutdown_tx.clone(),
39 };
40
41 let mut app = build_router_with_state(state);
42 if let Some(extra_router) = extra {
43 app = app.merge(extra_router);
44 }
45
46 let addr = format!("0.0.0.0:{}", port);
47 info!("Webhook server listening on {}", addr);
48
49 let listener = tokio::net::TcpListener::bind(&addr).await?;
50 axum::serve(listener, app)
51 .with_graceful_shutdown(async move {
52 shutdown_signal().await;
53 info!("Shutdown signal received — stopping webhook server");
54 let _ = shutdown_tx.send(true);
55 })
56 .await?;
57
58 Ok(())
59}
60
61pub fn build_router(db: DatabaseConnection) -> Router {
62 let (shutdown_tx, _) = watch::channel(false);
63 let state = WebAppState { db, shutdown_tx };
64 build_router_with_state(state)
65}
66
67fn build_router_with_state(state: WebAppState) -> Router {
68 let cors = CorsLayer::new()
69 .allow_origin("http://localhost:5173".parse::<HeaderValue>().unwrap())
70 .allow_methods([
71 Method::GET,
72 Method::POST,
73 Method::PUT,
74 Method::DELETE,
75 Method::OPTIONS,
76 ])
77 .allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE]);
78
79 Router::new()
80 .route("/webhook", post(agent_webhook))
81 .layer(cors)
82 .with_state(state)
83}
84
85async fn shutdown_signal() {
86 let ctrl_c = async {
87 tokio::signal::ctrl_c()
88 .await
89 .expect("Failed to install Ctrl+C handler");
90 };
91
92 #[cfg(unix)]
93 let terminate = async {
94 tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
95 .expect("Failed to install SIGTERM handler")
96 .recv()
97 .await;
98 };
99
100 #[cfg(not(unix))]
101 let terminate = std::future::pending::<()>();
102
103 tokio::select! {
104 _ = ctrl_c => {}
105 _ = terminate => {}
106 }
107}