brewlog/src/presentation/cli/roasts.rs
Jon Seager e01b6d0a1c
feat: add optional created_at to entity creation and updates
Add `created_at: Option<DateTime<Utc>>` through all layers so CLI users
can backdate entities at creation/update time. When omitted, falls back
to `Utc::now()`.

- Domain: add field to all New*/Update* structs with serde(default)
- Domain: timeline events use entity created_at instead of Utc::now()
- Repos: unify INSERT to explicit Rust-side created_at with unwrap_or_else
- Repos: add created_at to UPDATE dynamic query builders
- Routes: add field to submission structs and has_changes guards
- Clients: pass created_at through manual JSON client methods
- CLI: add --created-at flag with parse_created_at helper (RFC 3339 or YYYY-MM-DD)
2026-02-07 09:48:20 +00:00

138 lines
3.9 KiB
Rust

use anyhow::Result;
use clap::{Args, Subcommand};
use super::macros::{define_delete_command, define_get_command};
use super::parse_created_at;
use super::print_json;
use crate::domain::ids::{RoastId, RoasterId};
use crate::domain::roasts::{NewRoast, UpdateRoast};
use crate::infrastructure::client::BrewlogClient;
#[derive(Debug, Subcommand)]
pub enum RoastCommands {
/// Add a new roast
Add(AddRoastCommand),
/// List all roasts
List(ListRoastsCommand),
/// Get a roast by ID
Get(GetRoastCommand),
/// Update a roast
Update(UpdateRoastCommand),
/// Delete a roast
Delete(DeleteRoastCommand),
}
pub async fn run(client: &BrewlogClient, cmd: RoastCommands) -> Result<()> {
match cmd {
RoastCommands::Add(c) => add_roast(client, c).await,
RoastCommands::List(c) => list_roasts(client, c).await,
RoastCommands::Get(c) => get_roast(client, c).await,
RoastCommands::Update(c) => update_roast(client, c).await,
RoastCommands::Delete(c) => delete_roast(client, c).await,
}
}
#[derive(Debug, Args)]
pub struct AddRoastCommand {
#[arg(long)]
pub roaster_id: i64,
#[arg(long)]
pub name: String,
#[arg(long)]
pub origin: String,
#[arg(long)]
pub region: String,
#[arg(long)]
pub producer: String,
#[arg(long)]
pub process: String,
#[arg(long = "tasting-notes", required = true)]
pub tasting_notes: Vec<String>,
/// Override creation timestamp (e.g. 2025-08-05T10:00:00Z or 2025-08-05)
#[arg(long)]
pub created_at: Option<String>,
}
pub async fn add_roast(client: &BrewlogClient, command: AddRoastCommand) -> Result<()> {
let created_at = command
.created_at
.map(|s| parse_created_at(&s))
.transpose()?;
let payload = NewRoast {
roaster_id: RoasterId::new(command.roaster_id),
name: command.name,
origin: command.origin,
region: command.region,
producer: command.producer,
tasting_notes: command.tasting_notes,
process: command.process,
created_at,
};
let roast = client.roasts().create(&payload).await?;
print_json(&roast)
}
#[derive(Debug, Args)]
pub struct ListRoastsCommand {
#[arg(long)]
pub roaster_id: Option<i64>,
}
pub async fn list_roasts(client: &BrewlogClient, command: ListRoastsCommand) -> Result<()> {
let roasts = client
.roasts()
.list(command.roaster_id.map(RoasterId::new))
.await?;
print_json(&roasts)
}
define_get_command!(GetRoastCommand, get_roast, RoastId, roasts);
#[derive(Debug, Args)]
pub struct UpdateRoastCommand {
#[arg(long)]
pub id: i64,
#[arg(long)]
pub roaster_id: Option<i64>,
#[arg(long)]
pub name: Option<String>,
#[arg(long)]
pub origin: Option<String>,
#[arg(long)]
pub region: Option<String>,
#[arg(long)]
pub producer: Option<String>,
#[arg(long)]
pub process: Option<String>,
#[arg(long = "tasting-notes")]
pub tasting_notes: Option<Vec<String>>,
/// 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_roast(client: &BrewlogClient, command: UpdateRoastCommand) -> Result<()> {
let created_at = command
.created_at
.map(|s| parse_created_at(&s))
.transpose()?;
let payload = UpdateRoast {
roaster_id: command.roaster_id.map(RoasterId::new),
name: command.name,
origin: command.origin,
region: command.region,
producer: command.producer,
tasting_notes: command.tasting_notes,
process: command.process,
created_at,
};
let roast = client
.roasts()
.update(RoastId::new(command.id), &payload)
.await?;
print_json(&roast)
}
define_delete_command!(DeleteRoastCommand, delete_roast, RoastId, roasts, "roast");