Use default username 'me' and clean up query params
This commit is contained in:
parent
2d60bf7c45
commit
1000d5ea2d
@ -24,17 +24,20 @@ use time::OffsetDateTime;
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let client = BeeminderClient::new(std::env::var("BEEMINDER_API_KEY")?);
|
||||
|
||||
// username defaults to 'me'; use `with_username` to change it
|
||||
// let client = BeeminderClient::new("api-key").with_username("foo");
|
||||
|
||||
// Create a datapoint
|
||||
let datapoint = CreateDatapoint::new(42.0)
|
||||
.with_timestamp(OffsetDateTime::now_utc())
|
||||
.with_comment("Meditation session");
|
||||
|
||||
client.create_datapoint("username", "meditation", &datapoint).await?;
|
||||
client.create_datapoint("meditation", &datapoint).await?;
|
||||
|
||||
// Fetch recent datapoints
|
||||
let datapoints = client
|
||||
.get_datapoints("username", "meditation", Some("timestamp"), Some(10))
|
||||
.get_datapoints("meditation", Some("timestamp"), Some(10))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
|
@ -9,21 +9,18 @@ async fn main() {
|
||||
env::var("BEEMINDER_API_KEY").expect("BEEMINDER_API_KEY environment variable not set");
|
||||
|
||||
let client = BeeminderClient::new(api_key);
|
||||
match client.get_user("me").await {
|
||||
match client.get_user().await {
|
||||
Ok(user) => println!("{user:#?}"),
|
||||
Err(e) => println!("{e:#?}"),
|
||||
}
|
||||
|
||||
let since = datetime!(2024-12-13 20:00 UTC);
|
||||
match client.get_user_diff("me", since).await {
|
||||
match client.get_user_diff(since).await {
|
||||
Ok(user) => println!("{user:#?}"),
|
||||
Err(e) => println!("{e:#?}"),
|
||||
}
|
||||
|
||||
match client
|
||||
.get_datapoints("me", "meditation", None, Some(10))
|
||||
.await
|
||||
{
|
||||
match client.get_datapoints("meditation", None, Some(2)).await {
|
||||
Ok(datapoints) => println!("{datapoints:#?}"),
|
||||
Err(e) => println!("{e:#?}"),
|
||||
}
|
||||
@ -31,7 +28,7 @@ async fn main() {
|
||||
let d = CreateDatapoint::new(1.0)
|
||||
.with_comment("Test #hashtag datapoint")
|
||||
.with_requestid("unique-id-42");
|
||||
match client.create_datapoint("me", "meditation", &d).await {
|
||||
match client.create_datapoint("meditation", &d).await {
|
||||
Ok(datapoint) => println!("Added: {datapoint:#?}"),
|
||||
Err(e) => println!("{e:#?}"),
|
||||
}
|
||||
|
127
src/lib.rs
127
src/lib.rs
@ -13,25 +13,19 @@ pub struct BeeminderClient {
|
||||
client: Client,
|
||||
api_key: String,
|
||||
base_url: String,
|
||||
username: String,
|
||||
}
|
||||
|
||||
impl BeeminderClient {
|
||||
async fn request<T>(
|
||||
&self,
|
||||
endpoint: &str,
|
||||
params: Option<Vec<(&str, &str)>>,
|
||||
) -> Result<T, Error>
|
||||
async fn get<T, U>(&self, endpoint: &str, query: &U) -> Result<T, Error>
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
U: serde::ser::Serialize,
|
||||
{
|
||||
let mut query = vec![("auth_token", self.api_key.as_str())];
|
||||
if let Some(additional_params) = params {
|
||||
query.extend(additional_params);
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.get(format!("{}{}", self.base_url, endpoint))
|
||||
.query(&[("auth_token", self.api_key.as_str())])
|
||||
.query(&query)
|
||||
.send()
|
||||
.await?
|
||||
@ -39,57 +33,59 @@ impl BeeminderClient {
|
||||
response.json().await.map_err(Error::from)
|
||||
}
|
||||
|
||||
async fn post<T>(&self, endpoint: &str, params: Option<Vec<(&str, &str)>>) -> Result<T, Error>
|
||||
async fn post<T, U>(&self, endpoint: &str, query: &U) -> Result<T, Error>
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
U: serde::ser::Serialize,
|
||||
{
|
||||
let mut query = vec![("auth_token", self.api_key.as_str())];
|
||||
if let Some(additional_params) = params {
|
||||
query.extend(additional_params);
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.post(format!("{}{}", self.base_url, endpoint))
|
||||
.query(&query)
|
||||
.query(&[("auth_token", self.api_key.as_str())])
|
||||
.query(query)
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
|
||||
response.json().await.map_err(Error::from)
|
||||
}
|
||||
|
||||
/// Creates a new `BeeminderClient` with the given API key.
|
||||
/// Default username is set to 'me'.
|
||||
#[must_use]
|
||||
pub fn new(api_key: String) -> Self {
|
||||
Self {
|
||||
client: Client::new(),
|
||||
api_key,
|
||||
base_url: "https://www.beeminder.com/api/v1/".to_string(),
|
||||
username: "me".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves information about a user.
|
||||
/// Sets a username for this client.
|
||||
#[must_use]
|
||||
pub fn with_username(mut self, username: impl Into<String>) -> Self {
|
||||
self.username = username.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Retrieves user information for user associated with client.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the HTTP request fails or response cannot be parsed.
|
||||
pub async fn get_user(&self, username: &str) -> Result<UserInfo, Error> {
|
||||
self.request(&format!("users/{username}.json"), None).await
|
||||
pub async fn get_user(&self) -> Result<UserInfo, Error> {
|
||||
let endpoint = format!("users/{}.json", self.username);
|
||||
self.get(&endpoint, &()).await
|
||||
}
|
||||
|
||||
/// Retrieves detailed user information with changes since the specified timestamp.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the HTTP request fails or response cannot be parsed.
|
||||
pub async fn get_user_diff(
|
||||
&self,
|
||||
username: &str,
|
||||
diff_since: OffsetDateTime,
|
||||
) -> Result<UserInfoDiff, Error> {
|
||||
pub async fn get_user_diff(&self, diff_since: OffsetDateTime) -> Result<UserInfoDiff, Error> {
|
||||
let diff_since = diff_since.unix_timestamp().to_string();
|
||||
let params = vec![("diff_since", diff_since.as_str())];
|
||||
self.request(&format!("users/{username}.json"), Some(params))
|
||||
.await
|
||||
let query = [("diff_since", &diff_since)];
|
||||
let endpoint = format!("users/{}.json", self.username);
|
||||
self.get(&endpoint, &query).await
|
||||
}
|
||||
|
||||
/// Retrieves datapoints for a specific goal.
|
||||
@ -98,22 +94,20 @@ impl BeeminderClient {
|
||||
/// Returns an error if the HTTP request fails or response cannot be parsed.
|
||||
pub async fn get_datapoints(
|
||||
&self,
|
||||
username: &str,
|
||||
goal: &str,
|
||||
sort: Option<&str>,
|
||||
count: Option<u64>,
|
||||
) -> Result<Vec<Datapoint>, Error> {
|
||||
let mut params = Vec::new();
|
||||
params.push(("sort", sort.unwrap_or("timestamp")));
|
||||
let query: Vec<(&str, String)> = vec![
|
||||
Some(("sort", sort.unwrap_or("timestamp").to_string())),
|
||||
count.map(|c| ("count", c.to_string())),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
let count_str;
|
||||
if let Some(count) = count {
|
||||
count_str = count.to_string();
|
||||
params.push(("count", &count_str));
|
||||
}
|
||||
|
||||
let endpoint = format!("users/{username}/goals/{goal}/datapoints.json");
|
||||
self.request(&endpoint, Some(params)).await
|
||||
let endpoint = format!("users/{}/goals/{goal}/datapoints.json", self.username);
|
||||
self.get(&endpoint, &query).await
|
||||
}
|
||||
|
||||
/// Creates a new datapoint for a goal.
|
||||
@ -122,41 +116,16 @@ impl BeeminderClient {
|
||||
/// Returns an error if the HTTP request fails or response cannot be parsed.
|
||||
pub async fn create_datapoint(
|
||||
&self,
|
||||
username: &str,
|
||||
goal: &str,
|
||||
datapoint: &CreateDatapoint,
|
||||
) -> Result<Datapoint, Error> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
let value_str = datapoint.value.to_string();
|
||||
params.push(("value", value_str.as_str()));
|
||||
|
||||
let timestamp_str;
|
||||
if let Some(ts) = datapoint.timestamp {
|
||||
timestamp_str = ts.unix_timestamp().to_string();
|
||||
params.push(("timestamp", timestamp_str.as_str()));
|
||||
}
|
||||
|
||||
if let Some(ds) = &datapoint.daystamp {
|
||||
params.push(("daystamp", ds.as_str()));
|
||||
}
|
||||
|
||||
if let Some(c) = &datapoint.comment {
|
||||
params.push(("comment", c.as_str()));
|
||||
}
|
||||
|
||||
if let Some(rid) = &datapoint.requestid {
|
||||
params.push(("requestid", rid.as_str()));
|
||||
}
|
||||
|
||||
let endpoint = format!("users/{username}/goals/{goal}/datapoints.json");
|
||||
self.post(&endpoint, Some(params)).await
|
||||
let endpoint = format!("users/{}/goals/{goal}/datapoints.json", self.username);
|
||||
self.post(&endpoint, datapoint).await
|
||||
}
|
||||
|
||||
/// Deletes a specific datapoint for a user's goal.
|
||||
/// Deletes a specific datapoint for the user's goal.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `username` - The username of the user.
|
||||
/// * `goal` - The name of the goal.
|
||||
/// * `datapoint_id` - The ID of the datapoint to delete.
|
||||
///
|
||||
@ -164,11 +133,13 @@ impl BeeminderClient {
|
||||
/// Returns an error if the HTTP request fails or if the response cannot be parsed.
|
||||
pub async fn delete_datapoint(
|
||||
&self,
|
||||
username: &str,
|
||||
goal: &str,
|
||||
datapoint_id: &str,
|
||||
) -> Result<Datapoint, Error> {
|
||||
let endpoint = format!("users/{username}/goals/{goal}/datapoints/{datapoint_id}.json");
|
||||
let endpoint = format!(
|
||||
"users/{}/goals/{goal}/datapoints/{datapoint_id}.json",
|
||||
self.username
|
||||
);
|
||||
let query = vec![("auth_token", self.api_key.as_str())];
|
||||
|
||||
let response = self
|
||||
@ -182,21 +153,21 @@ impl BeeminderClient {
|
||||
response.json().await.map_err(Error::from)
|
||||
}
|
||||
|
||||
/// Retrieves all goals for a user.
|
||||
/// Retrieves all goals for the user.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the HTTP request fails or response cannot be parsed.
|
||||
pub async fn get_goals(&self, username: &str) -> Result<Vec<GoalSummary>, Error> {
|
||||
self.request(&format!("users/{username}/goals.json"), None)
|
||||
.await
|
||||
pub async fn get_goals(&self) -> Result<Vec<GoalSummary>, Error> {
|
||||
let endpoint = format!("users/{}/goals.json", self.username);
|
||||
self.get(&endpoint, &()).await
|
||||
}
|
||||
|
||||
/// Retrieves archived goals for a user.
|
||||
/// Retrieves archived goals for the user.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the HTTP request fails or response cannot be parsed.
|
||||
pub async fn get_archived_goals(&self, username: &str) -> Result<Vec<GoalSummary>, Error> {
|
||||
self.request(&format!("users/{username}/goals/archived.json"), None)
|
||||
.await
|
||||
pub async fn get_archived_goals(&self) -> Result<Vec<GoalSummary>, Error> {
|
||||
let endpoint = format!("users/{}/goals/archived.json", self.username);
|
||||
self.get(&endpoint, &()).await
|
||||
}
|
||||
}
|
||||
|
@ -119,11 +119,12 @@ pub struct Datapoint {
|
||||
|
||||
/// Parameters for creating or updating a datapoint
|
||||
#[must_use]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct CreateDatapoint {
|
||||
/// The value to record
|
||||
pub value: f64,
|
||||
/// Timestamp for the datapoint, defaults to now if None
|
||||
#[serde(with = "time::serde::timestamp::option")]
|
||||
pub timestamp: Option<OffsetDateTime>,
|
||||
/// Date string (e.g. "20150831"), alternative to timestamp
|
||||
pub daystamp: Option<String>,
|
||||
|
Loading…
Reference in New Issue
Block a user