Sync your own workout data from your "Strong" app
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

- added refresh and get_user route - added feature-toggle for unnecessary routes - moved api to dedicated file

+165 -68
+4 -1
Cargo.toml
··· 8 8 tokio = { version = "1", features = ["full"] } 9 9 serde_json = "1.0.139" 10 10 dotenv = "0.15" 11 - serde = { version = "1.0.218", features = ["derive"] } 11 + serde = { version = "1.0.218", features = ["derive"] } 12 + 13 + [features] 14 + full = []
+6 -67
src/main.rs
··· 1 - use reqwest::{Client, Url}; 2 - use serde_json::json; 3 - use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; 1 + use reqwest::Url; 4 2 use std::env; 5 3 use dotenv::dotenv; 6 4 use serde::Deserialize; 5 + use crate::strong_api::StrongApi; 6 + 7 + mod strong_api; 7 8 8 9 #[tokio::main] 9 10 async fn main() -> Result<(), Box<dyn std::error::Error>> { ··· 12 13 let password = env::var("STRONG_PASS").expect("STRONG_PASS must be set"); 13 14 let strong_backend = env::var("STRONG_BACKEND").expect("STRONG_BACKEND must be set"); 14 15 15 - let url = Url::parse(format!("{strong_backend}/auth/login").as_str()).ok().unwrap(); 16 + let url = Url::parse(&*strong_backend).ok().unwrap(); 16 17 17 18 let mut strong_api = StrongApi::new(url); 18 19 19 20 strong_api.login(username.as_str(), password.as_str()).await?; 21 + strong_api.get_user().await?; 20 22 21 23 dbg!(strong_api.access_token); 22 24 dbg!(strong_api.refresh_token); 23 25 dbg!(strong_api.user_id); 24 26 25 27 Ok(()) 26 - } 27 - 28 - pub struct StrongApi { 29 - url: Url, 30 - headers: HeaderMap, 31 - client: Client, 32 - refresh_token: Option<String>, 33 - access_token: Option<String>, 34 - user_id: Option<String>, 35 - } 36 - 37 - #[derive(Debug, Deserialize)] 38 - struct LoginResponse { 39 - #[serde(rename = "accessToken")] 40 - access_token: Option<String>, 41 - #[serde(rename = "refreshToken")] 42 - refresh_token: Option<String>, 43 - #[serde(rename = "userId")] 44 - user_id: Option<String>, 45 - } 46 - 47 - impl StrongApi { 48 - pub fn new(url: Url) -> Self { 49 - let mut headers = HeaderMap::new(); 50 - headers.insert(HeaderName::from_static("user-agent"), HeaderValue::from_static("Strong Android")); 51 - headers.insert(HeaderName::from_static("content-type"), HeaderValue::from_static("application/json")); 52 - headers.insert(HeaderName::from_static("accept"), HeaderValue::from_static("application/json")); 53 - headers.insert(HeaderName::from_static("x-client-build"), HeaderValue::from_static("600013")); 54 - headers.insert(HeaderName::from_static("x-client-platform"), HeaderValue::from_static("android")); 55 - 56 - Self { 57 - url, 58 - headers, 59 - client: Client::new(), 60 - refresh_token: None, 61 - access_token: None, 62 - user_id: None, 63 - } 64 - } 65 - 66 - pub async fn login(&mut self, username: &str, password: &str) -> Result<(), Box<dyn std::error::Error>> { 67 - let body = json!({ 68 - "usernameOrEmail": username, 69 - "password": password 70 - }); 71 - 72 - let response = self.client 73 - .post(self.url.clone()) 74 - .headers(self.headers.clone()) 75 - .json(&body) 76 - .send() 77 - .await?; 78 - 79 - let response_text = response.text().await?; 80 - 81 - let parsed: LoginResponse = serde_json::from_str(&response_text)?; 82 - 83 - self.access_token = parsed.access_token; 84 - self.refresh_token = parsed.refresh_token; 85 - self.user_id = parsed.user_id; 86 - 87 - Ok(()) 88 - } 89 28 }
+155
src/strong_api.rs
··· 1 + use std::fmt::format; 2 + use reqwest::{Client, header::{HeaderMap, HeaderName, HeaderValue}, Url}; 3 + use serde::Deserialize; 4 + use serde_json::json; 5 + 6 + #[derive(Debug)] 7 + pub struct StrongApi { 8 + url: Url, 9 + headers: HeaderMap, 10 + client: Client, 11 + pub(crate) refresh_token: Option<String>, 12 + pub(crate) access_token: Option<String>, 13 + pub(crate) user_id: Option<String>, 14 + } 15 + 16 + #[derive(Debug, Deserialize)] 17 + pub struct LoginResponse { 18 + #[serde(rename = "accessToken")] 19 + access_token: Option<String>, 20 + #[serde(rename = "refreshToken")] 21 + refresh_token: Option<String>, 22 + #[serde(rename = "userId")] 23 + user_id: Option<String>, 24 + } 25 + 26 + impl StrongApi { 27 + 28 + /// Creates a new StrongApi instance with the provided backend URL. 29 + pub fn new(url: Url) -> Self { 30 + let mut headers = HeaderMap::new(); 31 + headers.insert(HeaderName::from_static("user-agent"), HeaderValue::from_static("Strong Android")); 32 + headers.insert(HeaderName::from_static("content-type"), HeaderValue::from_static("application/json")); 33 + headers.insert(HeaderName::from_static("accept"), HeaderValue::from_static("application/json")); 34 + headers.insert(HeaderName::from_static("x-client-build"), HeaderValue::from_static("600013")); 35 + headers.insert(HeaderName::from_static("x-client-platform"), HeaderValue::from_static("android")); 36 + 37 + Self { 38 + url, 39 + headers, 40 + client: Client::new(), 41 + refresh_token: None, 42 + access_token: None, 43 + user_id: None, 44 + } 45 + } 46 + 47 + /// Logs in to the Strong backend using the provided username and password. 48 + pub async fn login(&mut self, username: &str, password: &str) -> Result<(), Box<dyn std::error::Error>> { 49 + let url = self.url.join("auth/login").unwrap(); 50 + 51 + let body = json!({ 52 + "usernameOrEmail": username, 53 + "password": password 54 + }); 55 + 56 + let response = self.client 57 + .post(url) 58 + .headers(self.headers.clone()) 59 + .json(&body) 60 + .send() 61 + .await?; 62 + 63 + let response_text = response.text().await?; 64 + 65 + let parsed: LoginResponse = serde_json::from_str(&response_text)?; 66 + 67 + self.access_token = parsed.access_token; 68 + self.refresh_token = parsed.refresh_token; 69 + self.user_id = parsed.user_id; 70 + 71 + Ok(()) 72 + } 73 + 74 + /// Refreshes the access token using the access and refresh token which were obtained during login. 75 + /// Should be called when you receive a 401 Unauthorized response from the Strong backend. 76 + pub async fn refresh(&mut self) -> Result<(), Box<dyn std::error::Error>> { 77 + let url = self.url.join("auth/login/refresh").unwrap(); 78 + 79 + let body = json!({ 80 + "accessToken": self.access_token, 81 + "refreshToken": self.refresh_token 82 + }); 83 + 84 + let response = self.client 85 + .post(url) 86 + .bearer_auth(self.access_token.clone().unwrap()) 87 + .headers(self.headers.clone()) 88 + .json(&body) 89 + .send() 90 + .await?; 91 + 92 + dbg!(response.status()); 93 + 94 + let response_text = response.text().await?; 95 + 96 + let parsed: LoginResponse = serde_json::from_str(&response_text)?; 97 + 98 + self.access_token = parsed.access_token; 99 + self.refresh_token = parsed.refresh_token; 100 + 101 + Ok(()) 102 + } 103 + 104 + #[cfg(feature = "full")] 105 + pub async fn refresh_by_tokens( 106 + &mut self, access_token: String, 107 + refresh_token: String 108 + ) -> Result<(), Box<dyn std::error::Error>> { 109 + let url = self.url.join("auth/login/refresh").unwrap(); 110 + 111 + let body = json!({ 112 + "accessToken": access_token.clone(), 113 + "refreshToken": refresh_token, 114 + }); 115 + 116 + let response = self.client 117 + .post(url) 118 + .bearer_auth(access_token) 119 + .headers(self.headers.clone()) 120 + .json(&body) 121 + .send() 122 + .await?; 123 + 124 + dbg!(response.status()); 125 + 126 + let response_text = response.text().await?; 127 + 128 + let parsed: LoginResponse = serde_json::from_str(&response_text)?; 129 + 130 + self.access_token = parsed.access_token; 131 + self.refresh_token = parsed.refresh_token; 132 + 133 + Ok(()) 134 + } 135 + 136 + pub async fn get_user(&self) -> Result<(), Box<dyn std::error::Error>> { 137 + let user_id = &*self.user_id.clone().unwrap(); 138 + let url = self.url.join(format!("api/users/{user_id}").as_str()).unwrap(); 139 + 140 + let response = self.client 141 + .get(url) 142 + .bearer_auth(self.access_token.clone().unwrap()) 143 + .headers(self.headers.clone()) 144 + .send() 145 + .await?; 146 + 147 + dbg!(response.status()); 148 + 149 + let response_text = response.text().await?; 150 + 151 + dbg!(response_text); 152 + 153 + Ok(()) 154 + } 155 + }