1use std::{process::Command, sync::Arc};
2
3use axum::{
4 Json, Router,
5 extract::{Path, State},
6 routing::any,
7};
8use serde::{Deserialize, Serialize};
9use tokio::net::TcpListener;
10
11#[derive(Debug, Deserialize, Serialize)]
12pub struct Config {
13 #[serde(default = "default_webhook_listen_address")]
14 addr: String,
15 hooks: Vec<Entry>,
16}
17
18fn default_webhook_listen_address() -> String {
19 format!("0.0.0.0:1234")
20}
21
22#[derive(Debug, Deserialize, Serialize)]
23pub struct Entry {
24 path: String,
25 command: String,
26 args: Vec<String>,
27}
28
29pub async fn run_server(cfg: Config) {
30 let listener = TcpListener::bind(cfg.addr.clone()).await.unwrap();
31 let state = Arc::new(cfg);
32 let app: Router = Router::new()
33 .route("/{*key}", any(handle_url))
34 .with_state(state);
35 axum::serve(listener, app).await.unwrap();
36}
37
38#[derive(Debug, Deserialize, Serialize)]
39#[serde(rename_all = "snake_case")]
40enum Response {
41 Success,
42 Failed,
43}
44
45#[axum::debug_handler]
46async fn handle_url(Path(key): Path<String>, State(cfg): State<Arc<Config>>) -> Json<Response> {
47 if let Some(entry) = cfg.hooks.iter().find(|it| it.path == key) {
48 println!("Running entry {}", key);
49 let mut command = Command::new(&entry.command);
50 command.args(entry.args.iter());
51 let res = command.status();
52
53 match res {
54 Ok(o) if o.success() => Json(Response::Success),
55 _ => Json(Response::Failed),
56 }
57 } else {
58 Json(Response::Failed)
59 }
60}