use crate::error; use crate::error::Error; use crate::models; use chrono::serde::ts_seconds; use chrono::DateTime; use chrono::Utc; use reqwest; use serde::Deserialize; use serde::Serialize; use serde_json::from_value; use serde_json::Value; #[derive(Serialize, Deserialize, Debug)] pub struct Token { #[serde(with = "ts_seconds")] pub expires_at: DateTime, pub refresh_token: String, pub access_token: String, } impl Token { pub fn update_model(&self, out: &mut models::StravaToken) { out.expires_at = self.expires_at.clone(); out.refresh_token = self.refresh_token.clone(); out.access_token = self.access_token.clone(); } } impl From<&models::StravaToken> for Token { fn from(t: &models::StravaToken) -> Token { Token { expires_at: t.expires_at, refresh_token: t.refresh_token.clone(), access_token: t.access_token.clone(), } } } pub trait StravaApi: Sync + Send { fn get( &self, method: &str, access_token: &str, params: &[(&str, &str)], ) -> Result; fn refresh_token(&self, token: &Token) -> Result; } pub struct StravaImpl { client: reqwest::blocking::Client, base_url: String, api_url: String, client_id: String, client_secret: String, } impl StravaImpl { pub fn new(client_id: String, client_secret: String) -> StravaImpl { StravaImpl { client: reqwest::blocking::Client::new(), base_url: "https://www.strava.com".to_string(), api_url: "/api/v3".to_string(), client_id, client_secret, } } } impl StravaApi for StravaImpl { fn get( &self, method: &str, access_token: &str, params: &[(&str, &str)], ) -> Result { let uri = format!("{}{}{}", self.base_url, self.api_url, method); let response = self .client .get(&uri) .bearer_auth(access_token) .query(params) .send()?; info!("StravaApi::get({}) returned {:?}", method, response); let status = response.status(); let json: Value = response.json()?; if !status.is_success() { return Err(From::from(error::StravaApiError::new(status, json))); } Ok(json) } fn refresh_token(&self, token: &Token) -> Result { let uri = format!("{}{}{}", self.base_url, self.api_url, "/oauth/token"); let params = [ ("client_id", self.client_id.as_str()), ("client_secret", self.client_secret.as_str()), ("grant_type", "refresh_token"), ("refresh_token", token.refresh_token.as_str()), ]; let response = self.client.post(&uri).form(¶ms).send()?; info!("StravaApi::refresh_token returned {:?}", response); let status = response.status(); let json: Value = response.json()?; if !status.is_success() { return Err(From::from(error::StravaApiError::new(status, json))); } from_value(json).map_err(From::from) } } pub fn exchange_token(client_id: &str, client_secret: &str, code: &str) -> Result { let client = reqwest::blocking::Client::new(); let params = [ ("client_id", client_id), ("client_secret", client_secret), ("code", code), ]; let uri = "https://www.strava.com/oauth/token"; let req = client.post(uri).form(¶ms); let json: Value = req.send().map(|r| r.json())??; from_value(json).map_err(From::from) }