garmin_cli/cli/commands/
weight.rs1use crate::client::GarminClient;
4use crate::config::CredentialStore;
5use crate::error::Result;
6use chrono::{Duration, Local};
7
8use super::auth::refresh_token;
9
10pub async fn list(from: Option<String>, to: Option<String>, profile: Option<String>) -> Result<()> {
12 let store = CredentialStore::new(profile)?;
13 let (oauth1, oauth2) = refresh_token(&store).await?;
14
15 let client = GarminClient::new(&oauth1.domain);
16
17 let today = Local::now().date_naive();
19 let end_date = to.unwrap_or_else(|| today.format("%Y-%m-%d").to_string());
20 let start_date =
21 from.unwrap_or_else(|| (today - Duration::days(30)).format("%Y-%m-%d").to_string());
22
23 let path = format!(
24 "/weight-service/weight/dateRange?startDate={}&endDate={}",
25 start_date, end_date
26 );
27
28 let data: serde_json::Value = client.get_json(&oauth2, &path).await?;
29
30 if let Some(entries) = data.get("dateWeightList").and_then(|v| v.as_array()) {
32 if entries.is_empty() {
33 println!("No weight entries found.");
34 return Ok(());
35 }
36
37 println!(
38 "{:<12} {:>10} {:>10} {:>8} {:>8} {:>8} {:>8}",
39 "Date", "Weight", "BMI", "Fat %", "Muscle %", "Water %", "Bone"
40 );
41 println!("{}", "-".repeat(75));
42
43 for entry in entries {
44 let date = entry
45 .get("calendarDate")
46 .and_then(|v| v.as_str())
47 .unwrap_or("-");
48
49 let weight = entry
50 .get("weight")
51 .and_then(|v| v.as_f64())
52 .map(|w| format!("{:.1} kg", w / 1000.0))
53 .unwrap_or_else(|| "-".to_string());
54
55 let bmi = entry
56 .get("bmi")
57 .and_then(|v| v.as_f64())
58 .map(|b| format!("{:.1}", b))
59 .unwrap_or_else(|| "-".to_string());
60
61 let body_fat = entry
62 .get("bodyFat")
63 .and_then(|v| v.as_f64())
64 .map(|f| format!("{:.1}", f))
65 .unwrap_or_else(|| "-".to_string());
66
67 let muscle_mass = entry
68 .get("muscleMass")
69 .and_then(|v| v.as_f64())
70 .map(|m| format!("{:.1}", m / 1000.0))
71 .unwrap_or_else(|| "-".to_string());
72
73 let body_water = entry
74 .get("bodyWater")
75 .and_then(|v| v.as_f64())
76 .map(|w| format!("{:.1}", w))
77 .unwrap_or_else(|| "-".to_string());
78
79 let bone_mass = entry
80 .get("boneMass")
81 .and_then(|v| v.as_f64())
82 .map(|b| format!("{:.1}", b / 1000.0))
83 .unwrap_or_else(|| "-".to_string());
84
85 println!(
86 "{:<12} {:>10} {:>10} {:>8} {:>8} {:>8} {:>8}",
87 date, weight, bmi, body_fat, muscle_mass, body_water, bone_mass
88 );
89 }
90
91 println!(
92 "\nShowing {} entries from {} to {}",
93 entries.len(),
94 start_date,
95 end_date
96 );
97 } else {
98 println!("{}", serde_json::to_string_pretty(&data)?);
100 }
101
102 Ok(())
103}
104
105pub async fn add(weight: f64, unit: &str, profile: Option<String>) -> Result<()> {
107 let store = CredentialStore::new(profile)?;
108 let (oauth1, oauth2) = refresh_token(&store).await?;
109
110 let client = GarminClient::new(&oauth1.domain);
111
112 let weight_grams = match unit.to_lowercase().as_str() {
114 "kg" => (weight * 1000.0) as i64,
115 "lbs" | "lb" => (weight * 453.592) as i64,
116 _ => {
117 return Err(crate::error::GarminError::invalid_response(
118 "Invalid unit. Use 'kg' or 'lbs'",
119 ));
120 }
121 };
122
123 let today = Local::now().format("%Y-%m-%d").to_string();
124 let timestamp = Local::now().timestamp_millis();
125
126 let body = serde_json::json!({
127 "dateTimestamp": timestamp,
128 "gmtTimestamp": timestamp,
129 "weight": weight_grams,
130 "unitKey": "kg",
131 "calendarDate": today
132 });
133
134 let path = "/weight-service/user-weight";
135
136 let response = client.post_json(&oauth2, path, &body).await?;
137
138 println!("Weight entry added: {:.1} {} on {}", weight, unit, today);
139
140 if let Some(id) = response.get("samplePk").and_then(|v| v.as_i64()) {
141 println!("Entry ID: {}", id);
142 }
143
144 Ok(())
145}
146
147pub async fn latest(profile: Option<String>) -> Result<()> {
149 let store = CredentialStore::new(profile)?;
150 let (oauth1, oauth2) = refresh_token(&store).await?;
151
152 let client = GarminClient::new(&oauth1.domain);
153
154 let path = "/weight-service/weight/latest";
155
156 let data: serde_json::Value = client.get_json(&oauth2, path).await?;
157
158 let date = data
159 .get("calendarDate")
160 .and_then(|v| v.as_str())
161 .unwrap_or("-");
162
163 let weight = data
164 .get("weight")
165 .and_then(|v| v.as_f64())
166 .map(|w| w / 1000.0);
167
168 let bmi = data.get("bmi").and_then(|v| v.as_f64());
169 let body_fat = data.get("bodyFat").and_then(|v| v.as_f64());
170
171 println!("Latest Weight Entry");
172 println!("{}", "-".repeat(30));
173 println!("Date: {}", date);
174 println!(
175 "Weight: {}",
176 weight
177 .map(|w| format!("{:.1} kg", w))
178 .unwrap_or("-".to_string())
179 );
180 println!(
181 "BMI: {}",
182 bmi.map(|b| format!("{:.1}", b)).unwrap_or("-".to_string())
183 );
184 println!(
185 "Body Fat: {}",
186 body_fat
187 .map(|f| format!("{:.1}%", f))
188 .unwrap_or("-".to_string())
189 );
190
191 Ok(())
192}