Skip to main content

robson_gateway_webhook/
lib.rs

1pub 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}