use reqwest::Client;
use std::sync::Arc;
use url::Url;
use crate::{
config::HighLevelConfig,
error::{Error, Result},
};
use super::token::TokenData;
const AUTH_BASE_URL: &str = "https://marketplace.gohighlevel.com";
const TOKEN_URL: &str = "https://services.leadconnectorhq.com/oauth/token";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccessType {
Online,
Offline,
}
impl AccessType {
pub fn as_str(self) -> &'static str {
match self {
AccessType::Online => "online",
AccessType::Offline => "offline",
}
}
}
pub struct OAuthClient {
config: Arc<HighLevelConfig>,
inner: Client,
}
impl OAuthClient {
pub fn new(config: Arc<HighLevelConfig>, inner: Client) -> Self {
Self { config, inner }
}
pub fn authorization_url(&self, scopes: &[&str], access_type: AccessType) -> String {
let mut url = Url::parse(AUTH_BASE_URL).expect("static URL is valid");
url.set_path("/oauth/chooselocation");
{
let mut q = url.query_pairs_mut();
q.append_pair("response_type", "code");
if let Some(redirect_uri) = &self.config.redirect_uri {
q.append_pair("redirect_uri", redirect_uri);
}
q.append_pair("client_id", &self.config.client_id);
q.append_pair("scope", &scopes.join(" "));
q.append_pair("access_type", access_type.as_str());
}
url.to_string()
}
pub async fn exchange_code(&self, code: &str) -> Result<TokenData> {
let redirect_uri = self.config.redirect_uri.clone().unwrap_or_default();
let params = [
("client_id", self.config.client_id.as_str()),
("client_secret", self.config.client_secret.as_str()),
("grant_type", "authorization_code"),
("code", code),
("redirect_uri", &redirect_uri),
];
self.fetch_token(¶ms).await
}
pub async fn refresh_token(&self, refresh_token: &str) -> Result<TokenData> {
let params = [
("client_id", self.config.client_id.as_str()),
("client_secret", self.config.client_secret.as_str()),
("grant_type", "refresh_token"),
("refresh_token", refresh_token),
];
self.fetch_token(¶ms).await
}
async fn fetch_token(&self, params: &[(&str, &str)]) -> Result<TokenData> {
let resp = self.inner.post(TOKEN_URL).form(params).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let message = resp.text().await.unwrap_or_default();
return Err(Error::Api { status, message });
}
let mut token: TokenData = resp.json().await?;
token.mark_issued_now();
Ok(token)
}
}