feat: add CLI update commands for brews and cups

Add UpdateBrewCommand and UpdateCupCommand to the CLI with all
updatable fields. Add corresponding update() methods to BrewsClient
and CupsClient.
This commit is contained in:
Jon Seager 2026-02-10 18:40:19 +00:00
parent 931859938a
commit 5f99ca0953
No known key found for this signature in database
4 changed files with 154 additions and 4 deletions

View file

@ -1,7 +1,7 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use crate::domain::brews::{BrewWithDetails, QuickNote}; use crate::domain::brews::{BrewWithDetails, QuickNote, UpdateBrew};
use crate::domain::ids::{BagId, BrewId, GearId}; use crate::domain::ids::{BagId, BrewId, GearId};
use super::BrewlogClient; use super::BrewlogClient;
@ -94,6 +94,19 @@ impl<'a> BrewsClient<'a> {
self.inner.handle_response(response).await self.inner.handle_response(response).await
} }
pub async fn update(&self, id: BrewId, payload: &UpdateBrew) -> Result<BrewWithDetails> {
let url = self.inner.endpoint(&format!("api/v1/brews/{id}"))?;
let response = self
.inner
.request(reqwest::Method::PUT, url)
.json(payload)
.send()
.await
.context("failed to issue update brew request")?;
self.inner.handle_response(response).await
}
pub async fn delete(&self, id: BrewId) -> Result<()> { pub async fn delete(&self, id: BrewId) -> Result<()> {
let url = self.inner.endpoint(&format!("api/v1/brews/{id}"))?; let url = self.inner.endpoint(&format!("api/v1/brews/{id}"))?;
let response = self let response = self

View file

@ -1,7 +1,7 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use reqwest::StatusCode; use reqwest::StatusCode;
use crate::domain::cups::{Cup, CupWithDetails, NewCup}; use crate::domain::cups::{Cup, CupWithDetails, NewCup, UpdateCup};
use crate::domain::ids::CupId; use crate::domain::ids::CupId;
use super::BrewlogClient; use super::BrewlogClient;
@ -52,6 +52,19 @@ impl<'a> CupsClient<'a> {
self.inner.handle_response(response).await self.inner.handle_response(response).await
} }
pub async fn update(&self, id: CupId, payload: &UpdateCup) -> Result<Cup> {
let url = self.inner.endpoint(&format!("api/v1/cups/{id}"))?;
let response = self
.inner
.request(reqwest::Method::PUT, url)
.json(payload)
.send()
.await
.context("failed to issue update cup request")?;
self.inner.handle_response(response).await
}
pub async fn delete(&self, id: CupId) -> Result<()> { pub async fn delete(&self, id: CupId) -> Result<()> {
let url = self.inner.endpoint(&format!("api/v1/cups/{id}"))?; let url = self.inner.endpoint(&format!("api/v1/cups/{id}"))?;
let response = self let response = self

View file

@ -4,7 +4,7 @@ use clap::{Args, Subcommand};
use super::macros::{define_delete_command, define_get_command}; use super::macros::{define_delete_command, define_get_command};
use super::parse_created_at; use super::parse_created_at;
use super::print_json; use super::print_json;
use crate::domain::brews::QuickNote; use crate::domain::brews::{QuickNote, UpdateBrew};
use crate::domain::ids::{BagId, BrewId, GearId}; use crate::domain::ids::{BagId, BrewId, GearId};
use crate::infrastructure::client::BrewlogClient; use crate::infrastructure::client::BrewlogClient;
@ -16,6 +16,8 @@ pub enum BrewCommands {
List(ListBrewsCommand), List(ListBrewsCommand),
/// Get a brew by ID /// Get a brew by ID
Get(GetBrewCommand), Get(GetBrewCommand),
/// Update a brew
Update(UpdateBrewCommand),
/// Delete a brew /// Delete a brew
Delete(DeleteBrewCommand), Delete(DeleteBrewCommand),
} }
@ -25,6 +27,7 @@ pub async fn run(client: &BrewlogClient, cmd: BrewCommands) -> Result<()> {
BrewCommands::Add(c) => add_brew(client, c).await, BrewCommands::Add(c) => add_brew(client, c).await,
BrewCommands::List(c) => list_brews(client, c).await, BrewCommands::List(c) => list_brews(client, c).await,
BrewCommands::Get(c) => get_brew(client, c).await, BrewCommands::Get(c) => get_brew(client, c).await,
BrewCommands::Update(c) => update_brew(client, c).await,
BrewCommands::Delete(c) => delete_brew(client, c).await, BrewCommands::Delete(c) => delete_brew(client, c).await,
} }
} }
@ -118,5 +121,87 @@ pub async fn list_brews(client: &BrewlogClient, command: ListBrewsCommand) -> Re
print_json(&brews) print_json(&brews)
} }
#[derive(Debug, Args)]
pub struct UpdateBrewCommand {
#[arg(long)]
pub id: i64,
/// ID of the bag to brew from
#[arg(long)]
pub bag_id: Option<i64>,
/// Amount of coffee in grams
#[arg(long)]
pub coffee_weight: Option<f64>,
/// ID of the grinder to use
#[arg(long)]
pub grinder_id: Option<i64>,
/// Grind setting (e.g., 6.0 or 7.5)
#[arg(long)]
pub grind_setting: Option<f64>,
/// ID of the brewer to use
#[arg(long)]
pub brewer_id: Option<i64>,
/// ID of the filter paper to use
#[arg(long)]
pub filter_paper_id: Option<i64>,
/// Volume of water in ml
#[arg(long)]
pub water_volume: Option<i32>,
/// Water temperature in Celsius
#[arg(long)]
pub water_temp: Option<f64>,
/// Quick notes (comma-separated: good,too-fast,too-slow,too-hot,under-extracted,over-extracted)
#[arg(long, value_delimiter = ',')]
pub quick_notes: Option<Vec<String>>,
/// Brew time in seconds (e.g., 150 for 2:30)
#[arg(long)]
pub brew_time: Option<i32>,
/// Override creation timestamp (e.g. 2025-08-05T10:00:00Z or 2025-08-05)
#[arg(long)]
pub created_at: Option<String>,
}
pub async fn update_brew(client: &BrewlogClient, command: UpdateBrewCommand) -> Result<()> {
let created_at = command
.created_at
.map(|s| parse_created_at(&s))
.transpose()?;
let quick_notes = command.quick_notes.map(|notes| {
notes
.iter()
.filter_map(|s| QuickNote::from_str_value(s))
.collect()
});
let payload = UpdateBrew {
bag_id: command.bag_id.map(BagId::new),
coffee_weight: command.coffee_weight,
grinder_id: command.grinder_id.map(GearId::new),
grind_setting: command.grind_setting,
brewer_id: command.brewer_id.map(GearId::new),
filter_paper_id: command.filter_paper_id.map(GearId::new),
water_volume: command.water_volume,
water_temp: command.water_temp,
quick_notes,
brew_time: command.brew_time,
created_at,
};
let brew = client
.brews()
.update(BrewId::new(command.id), &payload)
.await?;
print_json(&brew)
}
define_get_command!(GetBrewCommand, get_brew, BrewId, brews); define_get_command!(GetBrewCommand, get_brew, BrewId, brews);
define_delete_command!(DeleteBrewCommand, delete_brew, BrewId, brews, "brew"); define_delete_command!(DeleteBrewCommand, delete_brew, BrewId, brews, "brew");

View file

@ -4,7 +4,7 @@ use clap::{Args, Subcommand};
use super::macros::{define_delete_command, define_get_command}; use super::macros::{define_delete_command, define_get_command};
use super::parse_created_at; use super::parse_created_at;
use super::print_json; use super::print_json;
use crate::domain::cups::NewCup; use crate::domain::cups::{NewCup, UpdateCup};
use crate::domain::ids::{CafeId, CupId, RoastId}; use crate::domain::ids::{CafeId, CupId, RoastId};
use crate::infrastructure::client::BrewlogClient; use crate::infrastructure::client::BrewlogClient;
@ -16,6 +16,8 @@ pub enum CupCommands {
List, List,
/// Get a cup by ID /// Get a cup by ID
Get(GetCupCommand), Get(GetCupCommand),
/// Update a cup
Update(UpdateCupCommand),
/// Delete a cup /// Delete a cup
Delete(DeleteCupCommand), Delete(DeleteCupCommand),
} }
@ -25,6 +27,7 @@ pub async fn run(client: &BrewlogClient, cmd: CupCommands) -> Result<()> {
CupCommands::Add(c) => add_cup(client, c).await, CupCommands::Add(c) => add_cup(client, c).await,
CupCommands::List => list_cups(client).await, CupCommands::List => list_cups(client).await,
CupCommands::Get(c) => get_cup(client, c).await, CupCommands::Get(c) => get_cup(client, c).await,
CupCommands::Update(c) => update_cup(client, c).await,
CupCommands::Delete(c) => delete_cup(client, c).await, CupCommands::Delete(c) => delete_cup(client, c).await,
} }
} }
@ -60,5 +63,41 @@ pub async fn list_cups(client: &BrewlogClient) -> Result<()> {
print_json(&cups) print_json(&cups)
} }
#[derive(Debug, Args)]
pub struct UpdateCupCommand {
#[arg(long)]
pub id: i64,
/// ID of the roast
#[arg(long)]
pub roast_id: Option<i64>,
/// ID of the cafe
#[arg(long)]
pub cafe_id: Option<i64>,
/// Override creation timestamp (e.g. 2025-08-05T10:00:00Z or 2025-08-05)
#[arg(long)]
pub created_at: Option<String>,
}
pub async fn update_cup(client: &BrewlogClient, command: UpdateCupCommand) -> Result<()> {
let created_at = command
.created_at
.map(|s| parse_created_at(&s))
.transpose()?;
let payload = UpdateCup {
roast_id: command.roast_id.map(RoastId::new),
cafe_id: command.cafe_id.map(CafeId::new),
created_at,
};
let cup = client
.cups()
.update(CupId::new(command.id), &payload)
.await?;
print_json(&cup)
}
define_get_command!(GetCupCommand, get_cup, CupId, cups); define_get_command!(GetCupCommand, get_cup, CupId, cups);
define_delete_command!(DeleteCupCommand, delete_cup, CupId, cups, "cup"); define_delete_command!(DeleteCupCommand, delete_cup, CupId, cups, "cup");