diff --git a/hexstody-btc/Cargo.toml b/hexstody-btc/Cargo.toml index 7f18d8d..77426f3 100644 --- a/hexstody-btc/Cargo.toml +++ b/hexstody-btc/Cargo.toml @@ -22,7 +22,7 @@ thiserror = "1.0" tokio = { version = "1", features = ["full"] } uuid = { version = "0.8.2", features = ["v4"]} rocket = { version = "0.5.0-rc.1", default-features = false, features = ["json"] } -rocket_okapi = { git = "https://github.com/GREsau/okapi", branch = "master", features = ["swagger"]} +rocket_okapi = { git = "https://github.com/GREsau/okapi", branch = "master", features = ["swagger", "rapidoc"]} [dev-dependencies] maplit = "1.0.2" diff --git a/hexstody-btc/src/api/mod.rs b/hexstody-btc/src/api/mod.rs index 3244898..d3136c6 100644 --- a/hexstody-btc/src/api/mod.rs +++ b/hexstody-btc/src/api/mod.rs @@ -1 +1,2 @@ -pub mod public; \ No newline at end of file +pub mod public; +pub mod types; \ No newline at end of file diff --git a/hexstody-btc/src/api/public.rs b/hexstody-btc/src/api/public.rs index 70a387e..1d13b81 100644 --- a/hexstody-btc/src/api/public.rs +++ b/hexstody-btc/src/api/public.rs @@ -1,19 +1,39 @@ -use rocket::get; -use rocket::response::content; -use rocket_okapi::{openapi, openapi_get_routes, swagger_ui::*}; -use std::error::Error; +use super::types::*; +use rocket::figment::{providers::Env, Figment}; +use rocket::{get, serde::json::Json, Config}; +use rocket_okapi::settings::UrlObject; +use rocket_okapi::{openapi, openapi_get_routes, rapidoc::*, swagger_ui::*}; +use rocket::fairing::AdHoc; +use std::net::IpAddr; +use tokio::sync::Notify; use std::sync::Arc; -use tokio::sync::{Mutex, Notify}; -#[openapi(tag = "ping")] +#[openapi(tag = "misc")] #[get("/ping")] -fn json() -> content::Json<()> { - content::Json(()) +fn ping() -> Json<()> { + Json(()) } -pub async fn serve_public_api() -> () { - rocket::build() - .mount("/", openapi_get_routes![json]) +#[openapi(tag = "events")] +#[get("/deposit")] +async fn deposit() -> Json { + Json(DepositEvents { events: vec![] }) +} + +pub async fn serve_public_api(address: IpAddr, port: u16, start_notify: Arc) -> Result<(), rocket::Error> { + let figment = Figment::from(Config { + address, + port, + ..Config::default() + }) + .merge(Env::prefixed("HEXSTODY_BTC_").global()); + + let on_ready = AdHoc::on_liftoff("API Start!", |_| Box::pin(async move { + start_notify.notify_one(); + })); + + rocket::custom(figment) + .mount("/", openapi_get_routes![ping, deposit]) .mount( "/swagger/", make_swagger_ui(&SwaggerUIConfig { @@ -21,8 +41,25 @@ pub async fn serve_public_api() -> () { ..Default::default() }), ) + .mount( + "/rapidoc/", + make_rapidoc(&RapiDocConfig { + general: GeneralConfig { + spec_urls: vec![UrlObject::new("General", "../openapi.json")], + ..Default::default() + }, + hide_show: HideShowConfig { + allow_spec_url_load: false, + allow_spec_file_load: false, + ..Default::default() + }, + ..Default::default() + }), + ) + .attach(on_ready) .launch() - .await; + .await?; + Ok(()) } #[cfg(test)] @@ -34,7 +71,7 @@ mod tests { use hexstody_btc_client::client::BtcClient; use std::panic::AssertUnwindSafe; - const SERVICE_TEST_PORT: u16 = 8000; + const SERVICE_TEST_PORT: u16 = 8289; const SERVICE_TEST_HOST: &str = "127.0.0.1"; async fn run_api_test(test_body: F) @@ -43,16 +80,20 @@ mod tests { Fut: Future, { let _ = env_logger::builder().is_test(true).try_init(); + let start_notify = Arc::new(Notify::new()); let (sender, receiver) = tokio::sync::oneshot::channel(); tokio::spawn({ + let start_notify = start_notify.clone(); async move { - let serve_task = serve_public_api(); + let serve_task = + serve_public_api(SERVICE_TEST_HOST.parse().unwrap(), SERVICE_TEST_PORT, start_notify); futures::pin_mut!(serve_task); futures::future::select(serve_task, receiver.map_err(drop)).await; } }); - + start_notify.notified().await; + let res = AssertUnwindSafe(test_body()).catch_unwind().await; sender.send(()).unwrap(); @@ -60,6 +101,7 @@ mod tests { assert!(res.is_ok()); } + #[tokio::test] async fn test_public_api_ping() { run_api_test(|| async { let client = BtcClient::new(&format!( diff --git a/hexstody-btc/src/api/types.rs b/hexstody-btc/src/api/types.rs new file mode 100644 index 0000000..d7ea95f --- /dev/null +++ b/hexstody-btc/src/api/types.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; +use rocket_okapi::okapi::schemars; +use rocket_okapi::okapi::schemars::JsonSchema; + +#[derive(Debug, Serialize, Deserialize, JsonSchema)] +pub struct DepositEvents { + pub events: Vec, +} + +#[derive(Debug, Serialize, Deserialize, JsonSchema)] +pub struct DepositEvent { + pub address: String, + /// Sats amount + pub amount: u64, + /// 0 means unconfirmed + pub confirmations: u64, + /// UNIX timestamp when the event occured + pub timestamp: i64, +} diff --git a/hexstody-btc/src/main.rs b/hexstody-btc/src/main.rs index d94851e..309f38c 100644 --- a/hexstody-btc/src/main.rs +++ b/hexstody-btc/src/main.rs @@ -4,9 +4,10 @@ use clap::Parser; use futures::future::{AbortHandle, Abortable, Aborted}; use log::*; use std::error::Error; +use std::net::IpAddr; use std::sync::Arc; use std::time::Duration; -use tokio::sync::{Mutex, Notify}; +use tokio::sync::Notify; use tokio::time::sleep; use api::public::*; @@ -21,7 +22,17 @@ struct Args { #[derive(Parser, Debug, Clone)] enum SubCommand { /// Start listening incoming API requests - Serve + Serve { + #[clap(long, short, default_value = "8180", env = "HEXSTODY_BTC_API_PORT")] + port: u16, + #[clap( + long, + short, + default_value = "127.0.0.1", + env = "HEXSTODY_BTC_API_ADDRESS" + )] + address: IpAddr, + }, } #[tokio::main] @@ -30,15 +41,14 @@ async fn main() -> Result<(), Box> { env_logger::init(); match args.subcmd.clone() { - SubCommand::Serve => loop { - let args = args.clone(); + SubCommand::Serve { address, port } => loop { let (_abort_api_handle, abort_api_reg) = AbortHandle::new_pair(); info!("Serving API"); - - let public_api_fut = tokio::spawn(serve_public_api()); + let start_notify = Arc::new(Notify::new()); + let public_api_fut = tokio::spawn(serve_public_api(address, port, start_notify)); match Abortable::new(public_api_fut, abort_api_reg).await { - Ok(mres) => (), + Ok(_) => (), Err(Aborted) => { error!("API thread aborted") } @@ -47,7 +57,6 @@ async fn main() -> Result<(), Box> { let restart_dt = Duration::from_secs(5); info!("Adding {:?} delay before restarting logic", restart_dt); sleep(restart_dt).await; - } + }, } - Ok(()) } diff --git a/nix/pkgs.nix b/nix/pkgs.nix index 0eb8e8e..86cb610 100644 --- a/nix/pkgs.nix +++ b/nix/pkgs.nix @@ -2,6 +2,6 @@ import ((import {}).fetchFromGitHub { owner = "NixOS"; repo = "nixpkgs"; - rev = "7ec99ea7cf9616ef4c6e835710202623fcb846e7"; - sha256 = "1cp4sb4v1qzb268h2ky7039lf1gwkrs757q6gv2wd2hs65kvf1q7"; + rev = "21dcccd97d28520d5d22fb545bcdcc72b2cffcd2"; + sha256 = "0h9bwbsvkiqsrkswljnac8jibflfyb1rf3gx867g60qs8nia0n1y"; })