brewlog/src/infrastructure/client/gear.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

114 lines
3.1 KiB
Rust

use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use crate::domain::gear::{Gear, UpdateGear};
use crate::domain::ids::GearId;
use super::BrewlogClient;
pub struct GearClient<'a> {
inner: &'a BrewlogClient,
}
impl<'a> GearClient<'a> {
pub(crate) fn new(inner: &'a BrewlogClient) -> Self {
Self { inner }
}
pub async fn create(
&self,
category: &str,
make: String,
model: String,
created_at: Option<DateTime<Utc>>,
) -> Result<Gear> {
let url = self.inner.endpoint("api/v1/gear")?;
let mut payload = serde_json::json!({
"category": category,
"make": make,
"model": model,
});
if let Some(ts) = created_at {
payload["created_at"] = serde_json::json!(ts);
}
let response = self
.inner
.request(reqwest::Method::POST, url)
.json(&payload)
.send()
.await
.context("failed to issue create gear request")?;
self.inner.handle_response(response).await
}
pub async fn list(&self, category: Option<String>) -> Result<Vec<Gear>> {
let mut url = self.inner.endpoint("api/v1/gear")?;
if let Some(category) = category {
url.query_pairs_mut().append_pair("category", &category);
}
let response = self
.inner
.request(reqwest::Method::GET, url)
.send()
.await
.context("failed to issue list gear request")?;
self.inner.handle_response(response).await
}
pub async fn get(&self, id: GearId) -> Result<Gear> {
let url = self.inner.endpoint(&format!("api/v1/gear/{id}"))?;
let response = self
.inner
.request(reqwest::Method::GET, url)
.send()
.await
.context("failed to issue get gear request")?;
self.inner.handle_response(response).await
}
pub async fn update(
&self,
id: GearId,
make: Option<String>,
model: Option<String>,
created_at: Option<DateTime<Utc>>,
) -> Result<Gear> {
let url = self.inner.endpoint(&format!("api/v1/gear/{id}"))?;
let payload = UpdateGear {
make,
model,
created_at,
};
let response = self
.inner
.request(reqwest::Method::PUT, url)
.json(&payload)
.send()
.await
.context("failed to issue update gear request")?;
self.inner.handle_response(response).await
}
pub async fn delete(&self, id: GearId) -> Result<()> {
let url = self.inner.endpoint(&format!("api/v1/gear/{id}"))?;
let response = self
.inner
.request(reqwest::Method::DELETE, url)
.send()
.await
.context("failed to issue delete gear request")?;
if response.status().is_success() {
Ok(())
} else {
Err(self.inner.response_error(response).await)
}
}
}