diff --git a/.gitignore b/.gitignore index ea8c4bf..0b745e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +.env \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3e13150..55636a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,15 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + [[package]] name = "aliasable" version = "0.1.3" @@ -177,7 +186,7 @@ dependencies = [ "log", "parking", "polling", - "rustix", + "rustix 0.37.27", "slab", "socket2", "waker-fn", @@ -271,6 +280,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + [[package]] name = "block-buffer" version = "0.9.0" @@ -360,18 +375,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "console" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" -dependencies = [ - "encode_unicode", - "lazy_static", - "libc", - "windows-sys 0.45.0", -] - [[package]] name = "const_fn" version = "0.4.9" @@ -456,6 +459,16 @@ dependencies = [ "cipher", ] +[[package]] +name = "ctrlc" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" +dependencies = [ + "nix", + "windows-sys", +] + [[package]] name = "cynic" version = "3.2.2" @@ -550,6 +563,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "either" version = "1.9.0" @@ -557,10 +576,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] -name = "encode_unicode" -version = "0.3.6" +name = "env_logger" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] [[package]] name = "envy" @@ -578,7 +604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -826,6 +852,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "ident_case" version = "1.0.1" @@ -848,19 +880,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" -[[package]] -name = "insta" -version = "1.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc" -dependencies = [ - "console", - "lazy_static", - "linked-hash-map", - "similar", - "yaml-rust", -] - [[package]] name = "instant" version = "0.1.12" @@ -878,7 +897,18 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.48.0", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix 0.38.21", + "windows-sys", ] [[package]] @@ -905,30 +935,24 @@ dependencies = [ "log", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "linux-raw-sys" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" + [[package]] name = "log" version = "0.4.20" @@ -969,6 +993,17 @@ dependencies = [ "adler", ] +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "libc", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -1055,13 +1090,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", - "bitflags", + "bitflags 1.3.2", "cfg-if", "concurrent-queue", "libc", "log", "pin-project-lite", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1220,6 +1255,35 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "ring" version = "0.17.5" @@ -1231,7 +1295,7 @@ dependencies = [ "libc", "spin", "untrusted", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1249,12 +1313,25 @@ version = "0.37.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", - "linux-raw-sys", - "windows-sys 0.48.0", + "linux-raw-sys 0.3.8", + "windows-sys", +] + +[[package]] +name = "rustix" +version = "0.38.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys 0.4.11", + "windows-sys", ] [[package]] @@ -1398,12 +1475,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "similar" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597" - [[package]] name = "slab" version = "0.4.9" @@ -1429,10 +1500,14 @@ version = "0.1.0" dependencies = [ "anyhow", "argh", + "base64 0.21.5", + "ctrlc", "cynic", "cynic-codegen", + "dotenvy", + "env_logger", "envy", - "insta", + "log", "semver 1.0.20", "serde", "serde_json", @@ -1564,6 +1639,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.50" @@ -1890,43 +1974,28 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets", ] [[package]] @@ -1935,104 +2004,53 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] diff --git a/Cargo.toml b/Cargo.toml index 088d21e..dabeae6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,14 +7,18 @@ license = "AGPL-3.0-only" [dependencies] anyhow = "1" argh = "0.1" +base64 = "0.21.5" +ctrlc = { version = "3", features = ["termination"] } cynic = { version = "3", features = ["http-surf"] } +dotenvy = "0.15" envy = "0.4" semver = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" tiny_http = "0.12" +log = "0.4" +env_logger = "0.10" ureq = { version = "2", features = ["json"] } [build-dependencies] cynic-codegen = { version = "3" } -insta = "1" diff --git a/src/gitea.rs b/src/gitea.rs new file mode 100644 index 0000000..39d9e36 --- /dev/null +++ b/src/gitea.rs @@ -0,0 +1,49 @@ +use anyhow::Result; +use base64::{engine::general_purpose, Engine as _}; +use log::debug; + +pub struct GiteaAPI { + api_endpoint: String, + auth: String, +} + +impl GiteaAPI { + pub fn new(api_endpoint: &str, auth: &str) -> Self { + Self { + api_endpoint: api_endpoint.to_string(), + auth: basic_auth(auth), + } + } + + pub fn check_repositories(&self, project_ids: &[&str]) -> Result<()> { + for repo in project_ids { + let api_url = format!("{}/v1/repos/{}", self.api_endpoint, repo); + let res = ureq::get(&api_url) + .set("Authorization", &self.auth) + .call()?; + if res.status() != 200 { + let body = res.into_string()?; + return Err(anyhow::anyhow!( + "Error fetching repository {}: {}", + repo, + body + )); + } + debug!("Repository {} exists", repo); + } + Ok(()) + } + + pub fn sync(&self, project_id: &str) -> Result<()> { + let api_url = format!("{}/v1/repos/{}/mirror-sync", &self.api_endpoint, project_id); + ureq::post(&api_url) + .set("Authorization", &self.auth) + .call()?; + debug!("Synced repository {}", project_id); + Ok(()) + } +} + +fn basic_auth(auth: &str) -> String { + format!("Basic {}", general_purpose::STANDARD.encode(auth)) +} diff --git a/src/main.rs b/src/main.rs index 56ad54c..ea04576 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,14 @@ use anyhow::{anyhow, Result}; +use gitea::GiteaAPI; +use log::{debug, error, info}; use serde::Deserialize; -use tiny_http::{Header, Server}; +use std::{collections::HashMap, process::exit, sync::Arc}; +use tiny_http::{Request, Response, Server}; -mod webhook; +use crate::sourcehut::SourcehutAPI; + +mod gitea; +mod sourcehut; fn default_bind() -> String { "0.0.0.0:8000".to_string() @@ -14,33 +20,120 @@ fn default_sourcehut_api() -> String { #[derive(Deserialize, Debug)] struct Config { + /// Bind address #[serde(default = "default_bind")] bind: String, + /// Sourcehut API endpoint #[serde(default = "default_sourcehut_api")] sourcehut_api: String, + + /// Sourcehut API token (from https://meta.sr.ht/oauth2, needs `git.sr.ht/REPOSITORY` grant) + #[serde(default = "default_sourcehut_api")] sourcehut_token: String, + /// Base URL for Gitea API (without the /v1) gitea_api_base: String, + /// username:password for basic auth gitea_auth: String, + /// URL to set as sourcehut webhook target where this service is reacheable url: String, + /// Comma separated list of repository map from sourcehut to Gitea e.g. "sourcehutrepo:owner/gitearepo" repositories: String, } fn main() -> Result<()> { + let _ = dotenvy::dotenv(); + + env_logger::init(); + let config = envy::prefixed("MIRROR_").from_env::()?; + // Parse repository mapping into a hashmap + let repository_map: HashMap<&str, &str> = config + .repositories + .split(",") + .map(|s| s.split_once(":").unwrap()) + .collect(); + + if repository_map.is_empty() { + return Err(anyhow!("No repositories provided")); + } + + debug!("Repository map: {:?}", repository_map); + + // Set up Gitea API and check that provided repositories exist + let gitea = GiteaAPI::new(&config.gitea_api_base, &config.gitea_auth); + let repositories = repository_map.values().map(|&s| s).collect::>(); + gitea.check_repositories(&repositories)?; + // Set up webhooks via GQL API to sourcehut - let mut srht_api = webhook::SourceHutAPI::new(&config.sourcehut_api, &config.sourcehut_token); - srht_api.init_webhook(&config.url)?; + let mut srht = SourcehutAPI::new(&config.sourcehut_api, &config.sourcehut_token); + srht.init_webhook(&config.url)?; + let api = Arc::new(srht); + let r = api.clone(); + + // Set up termination handler to delete webhook + ctrlc::set_handler(move || { + _ = r.delete_webhook(); + exit(0) + }) + .expect("Error setting termination handler"); + + // Listen for webhooks and handle them let server = Server::http(config.bind).map_err(|err| anyhow!(err))?; - - let json = Header::from_bytes(&b"Content-Type"[..], &b"application/json"[..]).unwrap(); - - for request in server.incoming_requests() {} + for request in server.incoming_requests() { + debug!("Received request from {:?}", request.remote_addr()); + match handle_request(request) { + Ok(Some(repository)) => { + // Get mapped repository, if it exists + if let Some(mapped) = repository_map.get(repository.as_str()) { + info!("Syncing repository {}", mapped); + if let Err(err) = gitea.sync(&mapped) { + error!("Error syncing repository: {}", err); + } + } + } + Err(err) => { + error!("Error handling webhook: {}", err); + } + _ => { + // Invalid request, ignore + } + } + } Ok(()) } + +fn handle_request(mut request: Request) -> Result> { + // Make sure the content type is JSON + let is_json = request + .headers() + .iter() + .find(|h| h.field.as_str() == "Content-Type" && h.value == "application/json") + .ok_or_else(|| anyhow!("Invalid Content-Type header")); + + match is_json { + Ok(_) => { + let mut content = String::new(); + request.as_reader().read_to_string(&mut content)?; + let repository = SourcehutAPI::read_webhook_payload(&content)?; + request.respond(Response::empty(204))?; + + return Ok(Some(repository.name)); + } + Err(err) => { + debug!( + "Rejecting request because of invalid Content-Type header: {}", + err + ); + request.respond(Response::from_string(err.to_string()).with_status_code(400))? + } + } + + Ok(None) +} diff --git a/src/webhook.rs b/src/sourcehut.rs similarity index 56% rename from src/webhook.rs rename to src/sourcehut.rs index 8947295..f229bd6 100644 --- a/src/webhook.rs +++ b/src/sourcehut.rs @@ -1,5 +1,6 @@ use anyhow::Result; use cynic::{GraphQlResponse, MutationBuilder}; +use log::error; #[cynic::schema("sourcehut")] mod schema {} @@ -14,13 +15,9 @@ struct CreateWebhookVariables { struct CreateWebhook { #[arguments(config: { events: ["REPO_UPDATE"], query: r#"query { webhook { - uuid - event - date ... on RepositoryEvent { repository { name - updated } } } @@ -42,18 +39,44 @@ struct DeleteWebhookVariables { #[cynic(graphql_type = "Mutation", variables = "DeleteWebhookVariables")] struct DeleteWebhook { #[arguments(id: $id)] + #[allow(dead_code)] delete_user_webhook: WebhookSubscription, } -pub struct SourceHutAPI { +#[derive(cynic::QueryFragment, Debug)] +struct RepositoryEvent { + repository: Repository, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct Repository { + pub name: String, +} + +#[derive(serde::Deserialize, Debug)] +struct WebhookEventReceived { + webhook: RepositoryEvent, +} + +#[derive(cynic::Enum, Clone, Copy, Debug)] +enum WebhookEvent { + RepoCreated, + RepoUpdate, + RepoDeleted, +} + +#[derive(cynic::Scalar, Debug, Clone)] +pub struct Time(pub String); + +pub struct SourcehutAPI { api_endpoint: String, token: String, webhook_id: Option, } -fn parse_gql_response(response: GraphQlResponse) -> Result { +fn parse_gql_response(response: GraphQlResponse) -> Result { match response.data { - Some(data) => Ok(data.id), + Some(data) => Ok(data), None => { // Check for errors match response.errors { @@ -68,7 +91,7 @@ fn parse_gql_response(response: GraphQlResponse) -> Result< } } -impl SourceHutAPI { +impl SourcehutAPI { pub fn new(api_endpoint: &str, token: &str) -> Self { Self { api_endpoint: api_endpoint.to_string(), @@ -83,32 +106,37 @@ impl SourceHutAPI { }); let response = ureq::post(&self.api_endpoint) .set("Authorization", &format!("Bearer {}", &self.token)) - .send_json(&operation)? - .into_json::>()?; - - let webhook_id = parse_gql_response(response)?; - self.webhook_id = Some(webhook_id); + .send_json(operation)? + .into_json::>()?; + let webhook_ev = parse_gql_response(response)?; + self.webhook_id = Some(webhook_ev.create_user_webhook.id); Ok(()) } - fn delete_webhook(&self, id: i32) -> Result { - let operation = DeleteWebhook::build(DeleteWebhookVariables { id }); - let response = ureq::post(&self.api_endpoint) - .set("Authorization", &format!("Bearer {}", &self.token)) - .send_json(&operation)? - .into_json::>()?; + pub fn delete_webhook(&self) -> Result<()> { + if let Some(id) = self.webhook_id { + let operation = DeleteWebhook::build(DeleteWebhookVariables { id }); + let response = ureq::post(&self.api_endpoint) + .set("Authorization", &format!("Bearer {}", &self.token)) + .send_json(operation)? + .into_json::>()?; - parse_gql_response(response) + parse_gql_response(response)?; + } + Ok(()) + } + + pub fn read_webhook_payload(data: &str) -> Result { + let data = serde_json::from_str::>(data)?; + parse_gql_response(data).map(|ev| ev.webhook.repository) } } -impl Drop for SourceHutAPI { +impl Drop for SourcehutAPI { fn drop(&mut self) { - if let Some(id) = self.webhook_id { - if let Err(e) = self.delete_webhook(id) { - println!("Failed to delete webhook: {}", e); - } + if let Err(e) = self.delete_webhook() { + error!("Failed to delete webhook: {}", e); } } }