1pub mod http;
2use anyhow::{ensure, Context as AnyhyowContext, Result};
3use hop::{Hop, HopOptions};
4
5use self::http::HttpClient;
6use crate::commands::auth::login::util::{token_options, TokenType};
7use crate::commands::ignite::groups::utils::fetch_grouped_deployments;
8use crate::commands::ignite::types::Deployment;
9use crate::commands::ignite::utils::{get_all_deployments, get_deployment};
10use crate::config::EXEC_NAME;
11use crate::store::auth::Auth;
12use crate::store::context::Context;
13use crate::store::Store;
14
15#[derive(Debug)]
16pub struct State {
17 pub is_ci: bool,
18 pub auth: Auth,
19 pub ctx: Context,
20 pub http: HttpClient,
21 pub debug: bool,
22 pub hop: Hop,
23 token: Option<String>,
24 token_type: Option<TokenType>,
25}
26
27#[derive(Debug, Default)]
28pub struct StateOptions {
29 pub override_project: Option<String>,
30 pub override_token: Option<String>,
31 pub debug: bool,
32}
33
34impl State {
35 pub async fn new(options: StateOptions) -> Result<Self> {
36 let auth = Auth::new().await?;
38 let mut ctx = Context::new().await?;
39
40 ctx.project_override = options
42 .override_project
43 .or_else(|| ctx.default_project.clone());
44
45 let init_token = if let Some(override_token) = options.override_token {
47 Some(override_token)
48 } else if let Some(ref user) = ctx.default_user {
50 auth.authorized.get(user).map(|x| x.to_string())
51 } else {
53 None
54 };
55
56 let (token, token_type) = Self::handle_token(init_token)?;
57
58 let api_url = std::env::var("API_URL")
59 .ok()
60 .or_else(|| ctx.override_api_url.clone());
61
62 let http = HttpClient::new(token.clone(), api_url.clone());
64
65 let hop = Hop::new_with_options(HopOptions {
66 token: token.clone(),
67 api_url,
68 })?;
69
70 Ok(State {
71 is_ci: Self::check_if_ci(),
72 token_type,
73 token,
74 http,
75 hop,
76 auth,
77 ctx,
78 debug: options.debug,
79 })
80 }
81
82 fn handle_token(token: Option<String>) -> Result<(Option<String>, Option<TokenType>)> {
83 let token_type = token
84 .as_ref()
85 .map(|token| TokenType::from_token(token).context("Invalid token type"))
86 .transpose()?;
87
88 Ok((token, token_type))
89 }
90
91 fn check_if_ci() -> bool {
93 std::env::vars().any(|(key, _)| {
94 matches!(
95 key.as_str(),
96 "BUILD_NUMBER"
97 | "CONTINUOUS_INTEGRATION"
98 | "GITLAB_CI"
99 | "CIRCLECI"
100 | "APPVEYOR"
101 | "RUN_ID"
102 | "CI"
103 )
104 })
105 }
106
107 pub async fn login(&mut self, token: Option<String>) -> Result<()> {
109 ensure!(
110 token.is_some() || self.token.is_some(),
111 "You are not logged in. Please run `{} auth login` first.",
112 EXEC_NAME
113 );
114
115 if let Some(token) = token {
116 let (token, token_type) = Self::handle_token(Some(token))?;
117
118 self.token = token.clone();
119 self.token_type = token_type;
120 self.http = HttpClient::new(token, self.ctx.override_api_url.clone());
121 }
122
123 let response = token_options(self.http.clone(), self.token_type.clone()).await?;
124
125 if !response.email_verified {
126 log::warn!("You need to verify your email address before you can use Hop.")
127 }
128
129 self.ctx.current = Some(response);
131
132 if let Some(TokenType::Ptk) = self.token_type {
134 self.ctx.project_override = self.ctx.current.as_ref().map(|cur| cur.id.clone())
135 }
136
137 Ok(())
138 }
139
140 pub fn token(&self) -> Option<String> {
141 self.token.clone()
142 }
143
144 pub async fn get_deployment_by_name_or_id(&self, name_or_id: &str) -> Result<Deployment> {
145 if name_or_id.starts_with("deployment_") {
148 return get_deployment(&self.http, name_or_id).await;
149 }
150
151 let deployments =
152 get_all_deployments(&self.http, &self.ctx.current_project_error()?.id).await?;
153
154 let deployment = deployments
155 .into_iter()
156 .find(|d| d.name == name_or_id)
157 .context("Deployment not found")?;
158
159 Ok(deployment)
160 }
161
162 pub async fn get_deployment_by_opt_name_or_id(
163 &self,
164 name_or_id: Option<&str>,
165 ) -> Result<Deployment> {
166 if let Some(name_or_id) = name_or_id {
167 self.get_deployment_by_name_or_id(name_or_id).await
168 } else {
169 let (deployments_fmt, deployments, validator) =
170 fetch_grouped_deployments(self, false, true).await?;
171
172 let idx = loop {
173 let idx = dialoguer::Select::new()
174 .with_prompt("Select a deployment")
175 .items(&deployments_fmt)
176 .default(0)
177 .interact()?;
178
179 if let Ok(idx) = validator(idx) {
180 break idx;
181 }
182
183 console::Term::stderr().clear_last_lines(1)?
184 };
185
186 Ok(deployments[idx].clone())
187 }
188 }
189}