#![feature(try_blocks)] use std::sync::Arc; use anyhow::{Context, Result}; use reqwest::StatusCode; use warp::{Filter, Reply}; use warp::filters::BoxedFilter; use crate::model::{ config::Config, gitea::GiteaWebhook, }; use crate::model::sourcehut::{BuildManifest, SubmitBuildPayload, SubmitBuildResponse}; pub mod model; #[tokio::main] async fn main() -> Result<()> { let config_text = tokio::fs::read_to_string("config.toml") .await .context("could not read config")?; let config: Config = toml::from_str(&config_text).context("could not deserialise config")?; let config = Arc::new(config); warp::serve(receive_webhook(Arc::clone(&config))) .run(([0, 0, 0, 0], 12645)) .await; Ok(()) } fn receive_webhook(config: Arc) -> BoxedFilter<(impl Reply, )> { let path = config.web.path.clone(); let route = warp::path("receive") .and(warp::path(path)) .and(warp::path::end()) .and(warp::body::json()) .and_then(move |webhook: GiteaWebhook| { let config = Arc::clone(&config); async move { let res: Result = try { let client = reqwest::Client::new(); // download the manifest let mut manifest_url = url::Url::parse(&webhook.head_commit.url)?; let mut old_segments = manifest_url.path_segments().unwrap().map(ToString::to_string).collect::>(); old_segments.insert(old_segments.len() - 2, "raw".to_string()); old_segments.push(".build.yml".to_string()); manifest_url.path_segments_mut() .unwrap() .clear() .extend(old_segments); let manifest_resp = client.get(manifest_url).send().await?; if manifest_resp.status() != StatusCode::OK { "no manifest".to_string() } else { let manifest_text = manifest_resp.text().await?; // deserialize the manifest let mut manifest: BuildManifest = serde_yaml::from_str(&manifest_text)?; // add the source url let source_url = format!("{}#{}", webhook.repository.clone_url, webhook.head_commit.id); manifest.sources = Some(vec![source_url]); // submit the build let payload = SubmitBuildPayload { manifest: serde_yaml::to_string(&manifest)?, note: Some(format!("Submitted via Gitea webhook\n{}", webhook.head_commit.url)), tags: None, execute: None, secrets: None, }; let resp = client.post("https://builds.sr.ht/api/jobs") .header("Authorization", format!("Bearer {}", config.sourcehut.personal_access_token)) .json(&payload) .send() .await?; let resp: SubmitBuildResponse = resp.json().await?; let link = format!("https://builds.sr.ht/{}/job/{}", resp.owner.canonical_name, resp.id); println!("{}", link); link } }; res.map_err(|e| warp::reject::custom(Error(e))) } }); warp::post().and(route).boxed() } #[derive(Debug)] struct Error(anyhow::Error); impl warp::reject::Reject for Error {}