#[macro_use] extern crate tracing; use std::{convert::Infallible, str::FromStr, sync::Arc, time::Duration}; use anyhow::Result; use oauth2::Scope; use serde::Deserialize; use tokio::sync::Mutex; use url::Url; use warp::Filter; use xero_rs::KeyPair; lazy_static::lazy_static! { static ref REDIRECT_ARGS: Arc>> = Arc::new(Mutex::new(None)); } #[derive(Clone, Deserialize)] struct RedirectArgs { code: String, state: Option, } #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt() .with_env_filter("info,xero_rs=trace") .init(); tokio::spawn(async move { let redirect = warp::get() .and(warp::path("redirect")) .and(warp::query::()) .and_then(|args| async { *REDIRECT_ARGS.lock().await = Some(args); Ok("success".to_string()) as Result }); warp::serve(redirect).run(([127, 0, 0, 1], 4000)).await }); let key_pair = KeyPair::from_env(); let redirect_url = Url::from_str("http://localhost:4000/redirect")?; let (authorize_url, csrf_token) = xero_rs::Client::authorize_url( key_pair.clone(), redirect_url.clone(), vec![ Scope::new("openid".to_string()), Scope::new("profile".to_string()), Scope::new("email".to_string()), Scope::new("accounting.transactions.read".to_string()), ], ); info!("Sign in to Xero: {}", authorize_url.to_string()); info!("Waiting for redirect URL to be hit..."); let RedirectArgs { code, state } = loop { tokio::time::sleep(Duration::from_millis(10)).await; if let Some(args) = REDIRECT_ARGS.try_lock().ok().and_then(|c| c.clone()) { break args; } }; assert_eq!(&state.expect("missing state"), csrf_token.secret()); let mut client = xero_rs::Client::from_authorization_code(key_pair, redirect_url, code).await?; let connections = xero_rs::connection::list(&client).await?; info!("found client connections: {:#?}", connections); client.set_tenant(Some( connections.first().expect("no connections found").tenant_id, )); let invoices = xero_rs::invoice::list(&client).await?; info!("found invoices: {:#?}", invoices); Ok(()) }