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