diff --git a/cli/src/domain/constant.rs b/cli/src/domain/constant.rs index 9b38265..876b550 100644 --- a/cli/src/domain/constant.rs +++ b/cli/src/domain/constant.rs @@ -16,8 +16,7 @@ pub const URL_PROVIDED_IS_INVALID: &str = "URL provided is invalid"; pub const FORM_VALIDATION_FAILED: &str = "Form validation failed"; // Suggestions -pub const RUN_PICA_CONFIGURATION_SUG: &str = - "Run `pica configure` to createa a configuration file."; +pub const RUN_PICA_CONFIGURATION_SUG: &str = "Run `pica login` to create a configuration file."; pub const CHECK_INTERNET_CONNECTION_SUG: &str = "Check your internet connection and try again"; pub const CHECK_PARAMETERS_SUG: &str = "Check the parameters and try again"; pub const CHECK_LIMIT_SUG: &str = "Try with a smaller limit"; @@ -30,6 +29,7 @@ pub const CHECK_AVAILABLE_CONN_DEFS_SUG: &str = // Instructions pub const GO_TO_URL: &str = "Go to the following url: "; +pub const GO_TO_TERMINAL: &str = "Please continue in the terminal. Happy hacking!"; // Metadata pub const ABOUT: &str = "Build performant, high-converting native integrations with a few lines of code. By unlocking more integrations, you can onboard more customers and expand app usage, overnight."; diff --git a/cli/src/service/command.rs b/cli/src/service/command.rs index 562a3d5..d7b4e5a 100644 --- a/cli/src/service/command.rs +++ b/cli/src/service/command.rs @@ -1,10 +1,10 @@ -use super::{Server, handle_response, readline}; +use super::{Server, handle_response, open_browser, readline}; use crate::{ algebra::Handler, domain::{ ABOUT, AppContext, CHECK_AVAILABLE_CONN_DEFS_SUG, CHECK_LIMIT_SUG, CONN_DEF_NOT_FOUND_MESSAGE_ERR, CONNECTION_NOT_FOUND_MESSAGE_ERR, CliConfig, DEFAULT_LIMIT, - FORM_VALIDATION_FAILED, GO_TO_URL, HEADER_SECRET_KEY, LIMIT_GREATER_THAN_EXPECTED, + FORM_VALIDATION_FAILED, HEADER_SECRET_KEY, LIMIT_GREATER_THAN_EXPECTED, RUN_LIST_COMMANDS_SUG, ReadResponse, Step, URL_PROVIDED_IS_INVALID, }, }; @@ -16,9 +16,9 @@ use entities::{ use serde::Deserialize; use serde_json::{Value, json}; use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, fmt::{Display, Formatter}, - time::Duration, + hash::RandomState, }; use tabled::{Table, settings::Style}; use url::Url; @@ -44,8 +44,8 @@ impl Pica { pub enum Command { /// Manage connections via the CLI. Connection(Connection), - /// Configure the CLI. It truncates the configuration file and creates a new one. - Configure { + /// Configures the CLI. It truncates the configuration file and creates a new one. + Login { /// Base url of the API #[arg(short, long)] base: Option, @@ -138,7 +138,7 @@ impl Display for Environment { impl Handler for Pica { async fn load(&self) -> Result { match self.command() { - Command::Configure { base, api } => { + Command::Login { base, api } => { Server::start(base.clone(), api.clone()).await?; Ok(AppContext::new(CliConfig::load())) @@ -149,7 +149,7 @@ impl Handler for Pica { async fn validate(&self, ctx: &AppContext) -> Result { match &self.command { - Command::Configure { base, api } => { + Command::Login { base, api } => { if let Some(base) = base { Url::parse(base).map_err(|e| { ctx.printer().stderr::( @@ -196,38 +196,62 @@ impl Handler for Pica { async fn run(&self, ctx: &AppContext) -> Result { match &self.command { - Command::Configure { .. } => Ok(()), + Command::Login { .. } => Ok(()), Command::Connection(Connection { command }) => match command { ConnectionCommand::Create { platform, web, env } => { + let secret = match env { + Environment::Sandbox => ctx.config().keys().sandbox(), + Environment::Production => ctx.config().keys().production(), + }; + let url = format!( + "{}/public/v1/event-links/create-embed-token", + ctx.config().server().api() + ); + + let embed_defs = handle_response::( + ctx.http() + .post(url) + .json(&json!({})) + .header(HEADER_SECRET_KEY, secret) + .send() + .await, + ctx.printer(), + ) + .await?; + + let platforms: HashSet = HashSet::from_iter( + embed_defs + .link_settings + .connected_platforms + .iter() + .map(|c| c.r#type.clone()), + ); + match platform { - Some(platform) => { - if *web { - // https://development.picaos.com/connections#create - let url = format!( - "{}/connections#create={}", - ctx.config().server().base(), - platform - ); + Some(platform) if platforms.contains(platform) => { + let url = format!( + "{}/v1/public/connection-definitions?platform={}", + ctx.config().server().api(), + platform + ); - ctx.printer() - .stdout(&(GO_TO_URL.to_string() + url.as_str())); + let conn_def = handle_response::>( + ctx.http().get(url).send().await, + ctx.printer(), + ) + .await?; + let conn_def = conn_def.rows().first(); - tokio::time::sleep(Duration::from_secs(1)).await; - } else { + if *web || conn_def.is_some_and(|cd| cd.is_oauth()) { let url = format!( - "{}/v1/public/connection-definitions?platform={}", - ctx.config().server().api(), + "{}/connections#open={}", + ctx.config().server().base(), platform ); - match handle_response::>( - ctx.http().get(url).send().await, - ctx.printer(), - ) - .await? - .rows() - .first() - { + open_browser(ctx.printer(), url.to_string()); + } else { + match conn_def { Some(conn_def) => { let steps = Step::from(conn_def); @@ -261,28 +285,6 @@ impl Handler for Pica { }, )?; - let secret = match env { - Environment::Sandbox => ctx.config().keys().sandbox(), - Environment::Production => { - ctx.config().keys().production() - } - }; - let url = format!( - "{}/public/v1/event-links/create-embed-token", - ctx.config().server().api() - ); - - let embed_defs = handle_response::( - ctx.http() - .post(url) - .json(&json!({})) - .header(HEADER_SECRET_KEY, secret) - .send() - .await, - ctx.printer(), - ) - .await?; - let link_token = embed_defs.link_settings.event_inc_token; let url = format!( @@ -326,27 +328,7 @@ impl Handler for Pica { } } } - None => { - let secret = match env { - Environment::Sandbox => ctx.config().keys().sandbox(), - Environment::Production => ctx.config().keys().production(), - }; - let url = format!( - "{}/public/v1/event-links/create-embed-token", - ctx.config().server().api() - ); - - let embed_defs = handle_response::( - ctx.http() - .post(url) - .json(&json!({})) - .header(HEADER_SECRET_KEY, secret) - .send() - .await, - ctx.printer(), - ) - .await?; - + _ => { ctx.printer().stdout(CHECK_AVAILABLE_CONN_DEFS_SUG); ctx.printer().stdout( diff --git a/cli/src/service/helper.rs b/cli/src/service/helper.rs index f3bcbdd..100c55d 100644 --- a/cli/src/service/helper.rs +++ b/cli/src/service/helper.rs @@ -1,5 +1,5 @@ use super::{Pica, Printer}; -use crate::domain::{CHECK_INTERNET_CONNECTION_SUG, CHECK_PARAMETERS_SUG}; +use crate::domain::{CHECK_INTERNET_CONNECTION_SUG, CHECK_PARAMETERS_SUG, GO_TO_URL}; use clap::error::ErrorKind; use entities::{InternalError, PicaError}; use reqwest::{Error as ReqwestError, Response}; @@ -48,3 +48,27 @@ pub fn readline() -> Result { Ok(buffer) } + +pub fn open_browser(printer: &Printer, url: String) { + #[cfg(target_os = "macos")] + let result = std::process::Command::new("open") + .arg(url.as_str()) + .spawn() + .and_then(|mut a| a.wait()); + + #[cfg(target_os = "windows")] + let result = std::process::Command::new("explorer") + .arg(url.as_str()) + .spawn() + .and_then(|mut a| a.wait()); + + #[cfg(target_os = "linux")] + let result = std::process::Command::new("xdg-open") + .arg(url.as_str()) + .spawn() + .and_then(|mut a| a.wait()); + + if result.is_err() { + printer.stdout(&(GO_TO_URL.to_string() + url.as_str())); + } +} diff --git a/cli/src/service/server.rs b/cli/src/service/server.rs index c52d359..2230a58 100644 --- a/cli/src/service/server.rs +++ b/cli/src/service/server.rs @@ -1,12 +1,11 @@ use crate::{ - domain::Server as ConfigServer, domain::{ - CHECK_PORT_FOR_SERVER_SUG, CliConfig, DEFAULT_API, DEFAULT_BASE, GO_TO_URL, Http, Keys, - RUN_PICA_CONNECTION_LIST_SUG, + CHECK_PORT_FOR_SERVER_SUG, CliConfig, DEFAULT_API, DEFAULT_BASE, GO_TO_TERMINAL, Http, + Keys, RUN_PICA_CONNECTION_LIST_SUG, Server as ConfigServer, }, - service::{Pica, Printer}, + service::{Pica, Printer, open_browser}, }; -use axum::{Router, extract::Query, routing::get}; +use axum::{Router, extract::Query, response::Html, routing::get}; use clap::error::ErrorKind; use entities::{InternalError, PicaError, Unit}; use reqwest::Client; @@ -86,7 +85,9 @@ impl Server { InternalError::io_err(&format!("{e}"), None) })?; - printer.stdout(&(GO_TO_URL.to_string() + &url)); + open_browser(&printer, url.to_string()); + + // printer.stdout(&(GO_TO_URL.to_string() + &url)); let server = axum::serve(listener, router); let server_handle = tokio::spawn(async move { @@ -139,7 +140,7 @@ impl Server { printer: Printer, base_url: Option, api_url: Option, - ) -> Result { + ) -> Result, PicaError> { let url = format!( "{}/auth/github", api_url.clone().unwrap_or(DEFAULT_API.to_string()) @@ -196,6 +197,6 @@ impl Server { printer.stdout(RUN_PICA_CONNECTION_LIST_SUG); - Ok(()) + Ok(Html(format!("

{}

", GO_TO_TERMINAL))) } } diff --git a/entities/src/domain/connection/connection_definition.rs b/entities/src/domain/connection/connection_definition.rs index 1ab8f17..9952597 100644 --- a/entities/src/domain/connection/connection_definition.rs +++ b/entities/src/domain/connection/connection_definition.rs @@ -101,6 +101,10 @@ pub struct ModelSorting { } impl ConnectionDefinition { + pub fn is_oauth(&self) -> bool { + self.settings.oauth + } + pub fn to_connection_type(&self) -> super::ConnectionType { match self.r#type { ConnectionDefinitionType::Api => ConnectionType::Api {}, diff --git a/entities/src/domain/connection/connection_oauth_definition.rs b/entities/src/domain/connection/connection_oauth_definition.rs index f71246e..711e4cb 100644 --- a/entities/src/domain/connection/connection_oauth_definition.rs +++ b/entities/src/domain/connection/connection_oauth_definition.rs @@ -127,7 +127,7 @@ impl Settings { #[serde(rename_all = "camelCase")] #[tabled(rename_all = "PascalCase")] pub struct ConnectedPlatformSlim { - #[tabled(rename = "platform")] + #[tabled(rename = "Platform")] pub r#type: String, pub title: String, pub connection_definition_id: Id,