-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi.rs
More file actions
178 lines (164 loc) Β· 6.61 KB
/
Copy pathapi.rs
File metadata and controls
178 lines (164 loc) Β· 6.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
use async_trait::async_trait;
use serde::de::DeserializeOwned;
use ansi_term::Colour;
use tokio::sync::OnceCell;
use crate::models::{GraphQLRequest, GraphQLResponse, WorkoutRequest, WorkoutResponse, UserBasicInfoData, User};
use crate::formatters::STDERR_COLOR_ENABLED;
use crate::workouts::{write_cached_user_wants_kg, read_cached_user_wants_kg};
#[cfg_attr(tarpaulin, ignore)]
#[async_trait]
pub trait ApiClient: Send + Sync {
async fn login_request(&self, request: &GraphQLRequest) -> Result<GraphQLResponse<crate::models::LoginData>, Box<dyn std::error::Error>>;
async fn graphql_request<T: DeserializeOwned + 'static>(&self, token: &str, query: &str, variables: Option<serde_json::Value>) -> Result<GraphQLResponse<T>, Box<dyn std::error::Error>>;
async fn get_user_info(&self, token: &str) -> Result<crate::models::User, Box<dyn std::error::Error>>;
async fn user_wants_kg(&self, token: &str) -> bool;
}
fn log_verbose_request(query: &str, variables: Option<&serde_json::Value>, verbose: bool) {
if verbose {
let mut output = format!("Query:\n{}", query);
if let Some(vars) = variables {
output += &format!("\nVariables: {}", serde_json::to_string_pretty(vars).unwrap_or("Failed".to_string()));
}
let colored = if *STDERR_COLOR_ENABLED {
Colour::Blue.paint(output).to_string()
} else {
output
};
eprintln!("{}", colored);
}
}
fn log_verbose_response(text: &str, status: reqwest::StatusCode, verbose: bool) {
if verbose {
let colored = if status.is_success() {
if *STDERR_COLOR_ENABLED {
Colour::Green.paint(text).to_string()
} else {
text.to_string()
}
} else if *STDERR_COLOR_ENABLED {
Colour::Red.paint(text).to_string()
} else {
text.to_string()
};
eprintln!("{}", colored);
}
}
pub struct DataAccess<'a, C: ApiClient> {
pub client: &'a C,
pub token: Option<&'a str>,
pub uid: Option<u32>,
pub use_network: bool,
pub use_cache: bool,
pub write_cache: bool,
}
#[derive(Clone)]
pub struct ReqwestClient {
client: reqwest::Client,
verbose: bool,
user_info: OnceCell<crate::models::User>,
}
impl ReqwestClient {
pub fn new_with_verbose(verbose: bool) -> Self {
ReqwestClient {
client: reqwest::Client::new(),
verbose,
user_info: OnceCell::new(),
}
}
}
#[cfg_attr(tarpaulin, ignore)]
#[async_trait]
impl ApiClient for ReqwestClient {
async fn login_request(&self, request: &GraphQLRequest) -> Result<GraphQLResponse<crate::models::LoginData>, Box<dyn std::error::Error>> {
log_verbose_request(&request.query, Some(&serde_json::to_value(&request.variables).unwrap()), self.verbose);
let response = self.client
.post("https://weightxreps.net/api/graphql")
.json(request)
.send()
.await?;
let status = response.status();
let text = response.text().await?;
log_verbose_response(&text, status, self.verbose);
let body: GraphQLResponse<crate::models::LoginData> = serde_json::from_str(&text)?;
Ok(body)
}
async fn graphql_request<T: DeserializeOwned + 'static>(&self, token: &str, query: &str, variables: Option<serde_json::Value>) -> Result<GraphQLResponse<T>, Box<dyn std::error::Error>> {
log_verbose_request(query, variables.as_ref(), self.verbose);
let request_body = if let Some(vars) = variables {
serde_json::json!({ "query": query, "variables": vars })
} else {
serde_json::json!({ "query": query })
};
let response = self.client
.post("https://weightxreps.net/api/graphql")
.header("Authorization", format!("Bearer {}", token))
.json(&request_body)
.send()
.await?;
let status = response.status();
let text = response.text().await?;
log_verbose_response(&text, status, self.verbose);
let body: GraphQLResponse<T> = serde_json::from_str(&text)?;
Ok(body)
}
async fn get_user_info(&self, token: &str) -> Result<crate::models::User, Box<dyn std::error::Error>> {
let user = self.user_info.get_or_try_init(|| async {
let query = r#"
query {
getSession {
user {
usekg
}
}
}
"#;
let response: GraphQLResponse<UserBasicInfoData> = self.graphql_request(token, query, None).await?;
if let Some(errors) = response.errors {
return Err::<User, Box<dyn std::error::Error>>(format!("GraphQL errors: {:?}", errors).into());
}
// Default to kg if not available
if let Some(data) = response.data {
let mut usekg = 1;
if let Some(session) = data.get_session
&& let Some(val) = session.user.usekg {
write_cached_user_wants_kg(val != 0);
usekg = val;
}
Ok(User { usekg: Some(usekg) })
} else {
Err("No data in response".into())
}
}).await?;
Ok(user.clone())
}
async fn user_wants_kg(&self, token: &str) -> bool {
if let Some(val) = read_cached_user_wants_kg() {
return val;
}
let user = self.get_user_info(token).await;
match user {
Ok(ref u) => return u.usekg.unwrap_or(1) == 1,
Err(_) => return false
}
}
}
#[cfg_attr(tarpaulin, ignore)]
pub async fn login_request<C: ApiClient>(client: &C, request: &GraphQLRequest) -> Result<GraphQLResponse<crate::models::LoginData>, Box<dyn std::error::Error>> {
client.login_request(request).await
}
#[cfg_attr(tarpaulin, ignore)]
pub async fn graphql_request<T: DeserializeOwned + 'static, C: ApiClient>(client: &C, token: &str, query: &str, variables: Option<serde_json::Value>) -> Result<GraphQLResponse<T>, Box<dyn std::error::Error>> {
client.graphql_request(token, query, variables).await
}
#[cfg_attr(tarpaulin, ignore)]
#[allow(dead_code)]
pub async fn workout_request(client: &reqwest::Client, token: &str, request: &WorkoutRequest) -> Result<WorkoutResponse, Box<dyn std::error::Error>> {
let response = client
.post("https://weightxreps.net/api/graphql")
.header("Authorization", format!("Bearer {}", token))
.json(request)
.send()
.await?;
let body: WorkoutResponse = response.json().await?;
Ok(body)
}