refactor(cli): restructure commands from flat to nested subcommands

Change CLI structure from `brewlog {verb}-{entity}` to `brewlog {entity} {verb}`:
- brewlog add-roaster → brewlog roaster add
- brewlog list-bags → brewlog bag list
- brewlog create-token → brewlog token create
- etc.

Each entity module now owns its subcommand enum and dispatch logic,
simplifying main.rs and improving discoverability via `brewlog {entity} --help`.

- Add RoasterCommands, RoastCommands, BagCommands, GearCommands, TokenCommands enums
- Add run() dispatcher to each entity module
- Simplify top-level Commands enum to delegate to entity modules
- Update all CLI tests and bootstrap script
This commit is contained in:
Jon Seager 2026-02-02 17:02:46 +00:00
parent 4edc66f44a
commit 0090c4ba43
No known key found for this signature in database
14 changed files with 392 additions and 285 deletions

View file

@ -3,7 +3,7 @@ set -euo pipefail
cargo build cargo build
BREWLOG_TOKEN="$(./target/debug/brewlog create-token --name "bootstrap-token" --username admin --password password | grep -Po "BREWLOG_TOKEN=\K.+$")" BREWLOG_TOKEN="$(./target/debug/brewlog token create --name "bootstrap-token" --username admin --password password | grep -Po "BREWLOG_TOKEN=\K.+$")"
export BREWLOG_TOKEN export BREWLOG_TOKEN
if [[ -z "$BREWLOG_TOKEN" ]]; then if [[ -z "$BREWLOG_TOKEN" ]]; then
@ -12,15 +12,15 @@ if [[ -z "$BREWLOG_TOKEN" ]]; then
fi fi
# Tim Wendelboe (Norway) # Tim Wendelboe (Norway)
./target/debug/brewlog add-roaster \ ./target/debug/brewlog roaster add \
--name "Tim Wendelboe" \ --name "Tim Wendelboe" \
--country "Norway" \ --country "Norway" \
--city "Oslo" \ --city "Oslo" \
--homepage "https://timwendelboe.no" \ --homepage "https://timwendelboe.no" \
--notes "World-renowned Nordic micro-roastery dedicated to clarity and sustainability." --notes "World-renowned Nordic micro-roastery dedicated to clarity and sustainability."
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Tim Wendelboe") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Tim Wendelboe") | .id')" \
--name "Ben Saïd Natural" \ --name "Ben Saïd Natural" \
--origin "Ethiopia" \ --origin "Ethiopia" \
--region "Sidamo" \ --region "Sidamo" \
@ -28,8 +28,8 @@ fi
--process "Natural" \ --process "Natural" \
--tasting-notes "Bergamot, Apricot, Floral" --tasting-notes "Bergamot, Apricot, Floral"
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Tim Wendelboe") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Tim Wendelboe") | .id')" \
--name "Finca Tamana Washed" \ --name "Finca Tamana Washed" \
--origin "Colombia" \ --origin "Colombia" \
--region "El Pital, Huila" \ --region "El Pital, Huila" \
@ -39,15 +39,15 @@ fi
# Coffee Collective (Denmark) # Coffee Collective (Denmark)
./target/debug/brewlog add-roaster \ ./target/debug/brewlog roaster add \
--name "Coffee Collective" \ --name "Coffee Collective" \
--country "Denmark" \ --country "Denmark" \
--city "Copenhagen" \ --city "Copenhagen" \
--homepage "https://coffeecollective.dk" \ --homepage "https://coffeecollective.dk" \
--notes "Pioneers of transparency and sustainability; multi-time Nordic roaster award winners." --notes "Pioneers of transparency and sustainability; multi-time Nordic roaster award winners."
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Coffee Collective") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Coffee Collective") | .id')" \
--name "Daterra Sweet Collection" \ --name "Daterra Sweet Collection" \
--origin "Brazil" \ --origin "Brazil" \
--region "Cerrado" \ --region "Cerrado" \
@ -55,8 +55,8 @@ fi
--process "Pulped Natural" \ --process "Pulped Natural" \
--tasting-notes "Hazelnut, Milk Chocolate, Yellow Fruit" --tasting-notes "Hazelnut, Milk Chocolate, Yellow Fruit"
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Coffee Collective") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Coffee Collective") | .id')" \
--name "Kieni" \ --name "Kieni" \
--origin "Kenya" \ --origin "Kenya" \
--region "Nyeri" \ --region "Nyeri" \
@ -66,15 +66,15 @@ fi
# Drop Coffee (Sweden) # Drop Coffee (Sweden)
./target/debug/brewlog add-roaster \ ./target/debug/brewlog roaster add \
--name "Drop Coffee" \ --name "Drop Coffee" \
--country "Sweden" \ --country "Sweden" \
--city "Stockholm" \ --city "Stockholm" \
--homepage "https://dropcoffee.com" \ --homepage "https://dropcoffee.com" \
--notes "Award-winning Swedish roastery prized for its elegance and clean Scandinavian style." --notes "Award-winning Swedish roastery prized for its elegance and clean Scandinavian style."
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Drop Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Drop Coffee") | .id')" \
--name "La Linda" \ --name "La Linda" \
--origin "Bolivia" \ --origin "Bolivia" \
--region "Caranavi" \ --region "Caranavi" \
@ -82,8 +82,8 @@ fi
--process "Washed" \ --process "Washed" \
--tasting-notes "Red Apple, Caramel, Floral" --tasting-notes "Red Apple, Caramel, Floral"
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Drop Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Drop Coffee") | .id')" \
--name "El Sunzita" \ --name "El Sunzita" \
--origin "El Salvador" \ --origin "El Salvador" \
--region "Ahuachapan" \ --region "Ahuachapan" \
@ -93,15 +93,15 @@ fi
# La Cabra (Denmark) # La Cabra (Denmark)
./target/debug/brewlog add-roaster \ ./target/debug/brewlog roaster add \
--name "La Cabra" \ --name "La Cabra" \
--country "Denmark" \ --country "Denmark" \
--city "Aarhus" \ --city "Aarhus" \
--homepage "https://www.lacabra.dk" \ --homepage "https://www.lacabra.dk" \
--notes "Scandinavian minimalist roastery known for clarity and innovative sourcing." --notes "Scandinavian minimalist roastery known for clarity and innovative sourcing."
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="La Cabra") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="La Cabra") | .id')" \
--name "Halo Beriti" \ --name "Halo Beriti" \
--origin "Ethiopia" \ --origin "Ethiopia" \
--region "Yirgacheffe" \ --region "Yirgacheffe" \
@ -109,8 +109,8 @@ fi
--process "Washed" \ --process "Washed" \
--tasting-notes "Jasmine, Lemon, Stone Fruit" --tasting-notes "Jasmine, Lemon, Stone Fruit"
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="La Cabra") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="La Cabra") | .id')" \
--name "Cerro Azul" \ --name "Cerro Azul" \
--origin "Colombia" \ --origin "Colombia" \
--region "Valle del Cauca" \ --region "Valle del Cauca" \
@ -120,15 +120,15 @@ fi
# April Coffee (Denmark) # April Coffee (Denmark)
./target/debug/brewlog add-roaster \ ./target/debug/brewlog roaster add \
--name "April Coffee" \ --name "April Coffee" \
--country "Denmark" \ --country "Denmark" \
--city "Copenhagen" \ --city "Copenhagen" \
--homepage "https://aprilcoffeeroasters.com" \ --homepage "https://aprilcoffeeroasters.com" \
--notes "Modern approach to Nordic coffee, emphasizing transparency and traceability." --notes "Modern approach to Nordic coffee, emphasizing transparency and traceability."
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="April Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="April Coffee") | .id')" \
--name "El Salvador Pacamara" \ --name "El Salvador Pacamara" \
--origin "El Salvador" \ --origin "El Salvador" \
--region "Santa Ana" \ --region "Santa Ana" \
@ -136,8 +136,8 @@ fi
--process "Honey" \ --process "Honey" \
--tasting-notes "Grapefruit, Sugar Cane, Plum" --tasting-notes "Grapefruit, Sugar Cane, Plum"
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="April Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="April Coffee") | .id')" \
--name "Guji Highland" \ --name "Guji Highland" \
--origin "Ethiopia" \ --origin "Ethiopia" \
--region "Guji" \ --region "Guji" \
@ -147,15 +147,15 @@ fi
# Assembly Coffee (UK) # Assembly Coffee (UK)
./target/debug/brewlog add-roaster \ ./target/debug/brewlog roaster add \
--name "Assembly Coffee" \ --name "Assembly Coffee" \
--country "UK" \ --country "UK" \
--city "London" \ --city "London" \
--homepage "https://assemblycoffee.co.uk" \ --homepage "https://assemblycoffee.co.uk" \
--notes "Based in Brixton, Assembly focuses on collaborative sourcing and education." --notes "Based in Brixton, Assembly focuses on collaborative sourcing and education."
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Assembly Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Assembly Coffee") | .id')" \
--name "Kochere" \ --name "Kochere" \
--origin "Ethiopia" \ --origin "Ethiopia" \
--region "Yirgacheffe" \ --region "Yirgacheffe" \
@ -163,8 +163,8 @@ fi
--process "Washed" \ --process "Washed" \
--tasting-notes "Peach, Lemon, Jasmine" --tasting-notes "Peach, Lemon, Jasmine"
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Assembly Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Assembly Coffee") | .id')" \
--name "La Laja" \ --name "La Laja" \
--origin "Mexico" \ --origin "Mexico" \
--region "Veracruz" \ --region "Veracruz" \
@ -174,15 +174,15 @@ fi
# Square Mile (UK) # Square Mile (UK)
./target/debug/brewlog add-roaster \ ./target/debug/brewlog roaster add \
--name "Square Mile Coffee" \ --name "Square Mile Coffee" \
--country "UK" \ --country "UK" \
--city "London" \ --city "London" \
--homepage "https://squaremilecoffee.com" \ --homepage "https://squaremilecoffee.com" \
--notes "One of London's pioneers; delivers balanced and clear, fruit-forward coffees." --notes "One of London's pioneers; delivers balanced and clear, fruit-forward coffees."
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Square Mile Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Square Mile Coffee") | .id')" \
--name "Red Brick Espresso" \ --name "Red Brick Espresso" \
--origin "Blend" \ --origin "Blend" \
--region "Multiple Origins" \ --region "Multiple Origins" \
@ -190,8 +190,8 @@ fi
--process "Washed, Natural" \ --process "Washed, Natural" \
--tasting-notes "Berry, Chocolate, Citrus" --tasting-notes "Berry, Chocolate, Citrus"
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Square Mile Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Square Mile Coffee") | .id')" \
--name "Kamwangi" \ --name "Kamwangi" \
--origin "Kenya" \ --origin "Kenya" \
--region "Kirinyaga" \ --region "Kirinyaga" \
@ -201,15 +201,15 @@ fi
# Dak Coffee Roasters (Netherlands) # Dak Coffee Roasters (Netherlands)
./target/debug/brewlog add-roaster \ ./target/debug/brewlog roaster add \
--name "Dak Coffee Roasters" \ --name "Dak Coffee Roasters" \
--country "Netherlands" \ --country "Netherlands" \
--city "Amsterdam" \ --city "Amsterdam" \
--homepage "https://www.dakcoffeeroasters.com" \ --homepage "https://www.dakcoffeeroasters.com" \
--notes "Highly experimental Dutch roastery; celebrates vibrant acidity and alternative processing." --notes "Highly experimental Dutch roastery; celebrates vibrant acidity and alternative processing."
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Dak Coffee Roasters") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Dak Coffee Roasters") | .id')" \
--name "El Paraiso 92 Anaerobic" \ --name "El Paraiso 92 Anaerobic" \
--origin "Colombia" \ --origin "Colombia" \
--region "Cauca" \ --region "Cauca" \
@ -217,8 +217,8 @@ fi
--process "Thermal Shock Anaerobic" \ --process "Thermal Shock Anaerobic" \
--tasting-notes "Passionfruit, Raspberry, Yogurt" --tasting-notes "Passionfruit, Raspberry, Yogurt"
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Dak Coffee Roasters") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Dak Coffee Roasters") | .id')" \
--name "Oreti SL28" \ --name "Oreti SL28" \
--origin "Kenya" \ --origin "Kenya" \
--region "Kirinyaga" \ --region "Kirinyaga" \
@ -228,15 +228,15 @@ fi
# Bonanza Coffee (Germany) # Bonanza Coffee (Germany)
./target/debug/brewlog add-roaster \ ./target/debug/brewlog roaster add \
--name "Bonanza Coffee" \ --name "Bonanza Coffee" \
--country "Germany" \ --country "Germany" \
--city "Berlin" \ --city "Berlin" \
--homepage "https://www.bonanzacoffee.de" \ --homepage "https://www.bonanzacoffee.de" \
--notes "Pioneering Berlin roastery focused on brightness, balance, and freshness." --notes "Pioneering Berlin roastery focused on brightness, balance, and freshness."
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Bonanza Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Bonanza Coffee") | .id')" \
--name "Gatomboya" \ --name "Gatomboya" \
--origin "Kenya" \ --origin "Kenya" \
--region "Nyeri" \ --region "Nyeri" \
@ -244,8 +244,8 @@ fi
--process "Washed" \ --process "Washed" \
--tasting-notes "Blackcurrant, Lime, Tomato" --tasting-notes "Blackcurrant, Lime, Tomato"
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Bonanza Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Bonanza Coffee") | .id')" \
--name "Los Pirineos" \ --name "Los Pirineos" \
--origin "El Salvador" \ --origin "El Salvador" \
--region "Usulután" \ --region "Usulután" \
@ -255,15 +255,15 @@ fi
# Friedhats (Netherlands) # Friedhats (Netherlands)
./target/debug/brewlog add-roaster \ ./target/debug/brewlog roaster add \
--name "Friedhats" \ --name "Friedhats" \
--country "Netherlands" \ --country "Netherlands" \
--city "Amsterdam" \ --city "Amsterdam" \
--homepage "https://friedhats.com" \ --homepage "https://friedhats.com" \
--notes "Quirky branding meets serious, awarded, fruit-forward coffees from Amsterdam." --notes "Quirky branding meets serious, awarded, fruit-forward coffees from Amsterdam."
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Friedhats") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Friedhats") | .id')" \
--name "Sidamo Guji" \ --name "Sidamo Guji" \
--origin "Ethiopia" \ --origin "Ethiopia" \
--region "Guji" \ --region "Guji" \
@ -271,8 +271,8 @@ fi
--process "Natural" \ --process "Natural" \
--tasting-notes "Peach, Raspberry, Rosehip" --tasting-notes "Peach, Raspberry, Rosehip"
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Friedhats") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Friedhats") | .id')" \
--name "La Esmeralda Geisha" \ --name "La Esmeralda Geisha" \
--origin "Panama" \ --origin "Panama" \
--region "Boquete" \ --region "Boquete" \
@ -282,15 +282,15 @@ fi
# Origin Coffee (UK) # Origin Coffee (UK)
./target/debug/brewlog add-roaster \ ./target/debug/brewlog roaster add \
--name "Origin Coffee" \ --name "Origin Coffee" \
--country "UK" \ --country "UK" \
--city "Porthleven" \ --city "Porthleven" \
--homepage "https://origincoffee.co.uk" \ --homepage "https://origincoffee.co.uk" \
--notes "Specialty roaster with close partnerships at origin; leading UK scene with cutting-edge lots." --notes "Specialty roaster with close partnerships at origin; leading UK scene with cutting-edge lots."
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Origin Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Origin Coffee") | .id')" \
--name "San Fermin" \ --name "San Fermin" \
--origin "Colombia" \ --origin "Colombia" \
--region "Tolima" \ --region "Tolima" \
@ -298,8 +298,8 @@ fi
--process "Washed" \ --process "Washed" \
--tasting-notes "Red Grape, Caramel, Blood Orange" --tasting-notes "Red Grape, Caramel, Blood Orange"
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Origin Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Origin Coffee") | .id')" \
--name "Aricha" \ --name "Aricha" \
--origin "Ethiopia" \ --origin "Ethiopia" \
--region "Yirgacheffe" \ --region "Yirgacheffe" \
@ -309,15 +309,15 @@ fi
# Dark Arts Coffee (UK) # Dark Arts Coffee (UK)
./target/debug/brewlog add-roaster \ ./target/debug/brewlog roaster add \
--name "Dark Arts Coffee" \ --name "Dark Arts Coffee" \
--country "UK" \ --country "UK" \
--city "London" \ --city "London" \
--homepage "https://www.darkartscoffee.co.uk" \ --homepage "https://www.darkartscoffee.co.uk" \
--notes "Playful, disruptive roaster with a cult following and flavor-forward offerings." --notes "Playful, disruptive roaster with a cult following and flavor-forward offerings."
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Dark Arts Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Dark Arts Coffee") | .id')" \
--name "Death to Decaf" \ --name "Death to Decaf" \
--origin "Brazil" \ --origin "Brazil" \
--region "Minas Gerais" \ --region "Minas Gerais" \
@ -325,8 +325,8 @@ fi
--process "Swiss Water Decaf" \ --process "Swiss Water Decaf" \
--tasting-notes "Cocoa, Cherry, Almond" --tasting-notes "Cocoa, Cherry, Almond"
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Dark Arts Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Dark Arts Coffee") | .id')" \
--name "Snoop" \ --name "Snoop" \
--origin "Guatemala" \ --origin "Guatemala" \
--region "Huehuetenango" \ --region "Huehuetenango" \
@ -336,15 +336,15 @@ fi
# KAWA Coffee (France) # KAWA Coffee (France)
./target/debug/brewlog add-roaster \ ./target/debug/brewlog roaster add \
--name "KAWA Coffee" \ --name "KAWA Coffee" \
--country "France" \ --country "France" \
--city "Paris" \ --city "Paris" \
--homepage "https://www.kawa.coffee" \ --homepage "https://www.kawa.coffee" \
--notes "One of Paris most exciting specialty roasteries, known for unusual and competition-level lots." --notes "One of Paris most exciting specialty roasteries, known for unusual and competition-level lots."
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="KAWA Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="KAWA Coffee") | .id')" \
--name "Sudan Rume" \ --name "Sudan Rume" \
--origin "Colombia" \ --origin "Colombia" \
--region "Cauca" \ --region "Cauca" \
@ -352,8 +352,8 @@ fi
--process "Natural" \ --process "Natural" \
--tasting-notes "Strawberry, Cinnamon, Grape" --tasting-notes "Strawberry, Cinnamon, Grape"
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="KAWA Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="KAWA Coffee") | .id')" \
--name "Arbegona" \ --name "Arbegona" \
--origin "Ethiopia" \ --origin "Ethiopia" \
--region "Sidama" \ --region "Sidama" \
@ -363,15 +363,15 @@ fi
# Stow Coffee (Slovenia) # Stow Coffee (Slovenia)
./target/debug/brewlog add-roaster \ ./target/debug/brewlog roaster add \
--name "Stow Coffee" \ --name "Stow Coffee" \
--country "Slovenia" \ --country "Slovenia" \
--city "Ljubljana" \ --city "Ljubljana" \
--homepage "https://www.stowcoffee.com" \ --homepage "https://www.stowcoffee.com" \
--notes "Slovenias specialty leader, awarded for pure, brightly acidic profiles and innovation." --notes "Slovenias specialty leader, awarded for pure, brightly acidic profiles and innovation."
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Stow Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Stow Coffee") | .id')" \
--name "Santa Barbara" \ --name "Santa Barbara" \
--origin "Honduras" \ --origin "Honduras" \
--region "Santa Barbara" \ --region "Santa Barbara" \
@ -379,8 +379,8 @@ fi
--process "Honey" \ --process "Honey" \
--tasting-notes "Red Currant, Honeydew, Cocoa" --tasting-notes "Red Currant, Honeydew, Cocoa"
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Stow Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Stow Coffee") | .id')" \
--name "Suke Quto" \ --name "Suke Quto" \
--origin "Ethiopia" \ --origin "Ethiopia" \
--region "Guji" \ --region "Guji" \
@ -390,15 +390,15 @@ fi
# Bows Coffee (Canada) # Bows Coffee (Canada)
./target/debug/brewlog add-roaster \ ./target/debug/brewlog roaster add \
--name "Bows Coffee" \ --name "Bows Coffee" \
--country "Canada" \ --country "Canada" \
--city "Victoria" \ --city "Victoria" \
--homepage "https://bowscoffee.com" \ --homepage "https://bowscoffee.com" \
--notes "Canadian micro-roaster with focus on clarity, complexity, and ethical sourcing." --notes "Canadian micro-roaster with focus on clarity, complexity, and ethical sourcing."
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Bows Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Bows Coffee") | .id')" \
--name "La Chumeca" \ --name "La Chumeca" \
--origin "Costa Rica" \ --origin "Costa Rica" \
--region "Tarrazú" \ --region "Tarrazú" \
@ -406,8 +406,8 @@ fi
--process "White Honey" \ --process "White Honey" \
--tasting-notes "Mandarin, Honeycomb, Almond" --tasting-notes "Mandarin, Honeycomb, Almond"
./target/debug/brewlog add-roast \ ./target/debug/brewlog roast add \
--roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Bows Coffee") | .id')" \ --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Bows Coffee") | .id')" \
--name "Simbi" \ --name "Simbi" \
--origin "Rwanda" \ --origin "Rwanda" \
--region "Huye" \ --region "Huye" \
@ -420,74 +420,74 @@ fi
# ============================================================================ # ============================================================================
# Tim Wendelboe - Ben Saïd Natural (250g) # Tim Wendelboe - Ben Saïd Natural (250g)
./target/debug/brewlog add-bag \ ./target/debug/brewlog bag add \
--roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Ben Saïd Natural") | .id')" \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Ben Saïd Natural") | .id')" \
--roast-date "2026-01-15" \ --roast-date "2026-01-15" \
--amount 250 --amount 250
# Tim Wendelboe - Finca Tamana Washed (350g) # Tim Wendelboe - Finca Tamana Washed (350g)
./target/debug/brewlog add-bag \ ./target/debug/brewlog bag add \
--roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Finca Tamana Washed") | .id')" \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Finca Tamana Washed") | .id')" \
--roast-date "2026-01-18" \ --roast-date "2026-01-18" \
--amount 350 --amount 350
# Coffee Collective - Daterra Sweet Collection (200g) # Coffee Collective - Daterra Sweet Collection (200g)
./target/debug/brewlog add-bag \ ./target/debug/brewlog bag add \
--roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Daterra Sweet Collection") | .id')" \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Daterra Sweet Collection") | .id')" \
--roast-date "2026-01-10" \ --roast-date "2026-01-10" \
--amount 200 --amount 200
# Drop Coffee - La Linda (500g) # Drop Coffee - La Linda (500g)
./target/debug/brewlog add-bag \ ./target/debug/brewlog bag add \
--roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="La Linda") | .id')" \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="La Linda") | .id')" \
--roast-date "2026-01-20" \ --roast-date "2026-01-20" \
--amount 500 --amount 500
# La Cabra - Halo Beriti (150g) # La Cabra - Halo Beriti (150g)
./target/debug/brewlog add-bag \ ./target/debug/brewlog bag add \
--roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Halo Beriti") | .id')" \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Halo Beriti") | .id')" \
--roast-date "2026-01-12" \ --roast-date "2026-01-12" \
--amount 150 --amount 150
# April Coffee - Guji Highland (300g) # April Coffee - Guji Highland (300g)
./target/debug/brewlog add-bag \ ./target/debug/brewlog bag add \
--roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Guji Highland") | .id')" \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Guji Highland") | .id')" \
--roast-date "2026-01-22" \ --roast-date "2026-01-22" \
--amount 300 --amount 300
# Assembly Coffee - Kochere (250g) # Assembly Coffee - Kochere (250g)
./target/debug/brewlog add-bag \ ./target/debug/brewlog bag add \
--roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Kochere") | .id')" \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Kochere") | .id')" \
--roast-date "2026-01-08" \ --roast-date "2026-01-08" \
--amount 250 --amount 250
# Square Mile Coffee - Red Brick Espresso (400g) # Square Mile Coffee - Red Brick Espresso (400g)
./target/debug/brewlog add-bag \ ./target/debug/brewlog bag add \
--roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Red Brick Espresso") | .id')" \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Red Brick Espresso") | .id')" \
--roast-date "2026-01-25" \ --roast-date "2026-01-25" \
--amount 400 --amount 400
# Dak Coffee Roasters - El Paraiso 92 Anaerobic (100g - small competition lot) # Dak Coffee Roasters - El Paraiso 92 Anaerobic (100g - small competition lot)
./target/debug/brewlog add-bag \ ./target/debug/brewlog bag add \
--roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="El Paraiso 92 Anaerobic") | .id')" \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="El Paraiso 92 Anaerobic") | .id')" \
--roast-date "2026-01-28" \ --roast-date "2026-01-28" \
--amount 100 --amount 100
# Bonanza Coffee - Gatomboya (175g) # Bonanza Coffee - Gatomboya (175g)
./target/debug/brewlog add-bag \ ./target/debug/brewlog bag add \
--roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Gatomboya") | .id')" \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Gatomboya") | .id')" \
--roast-date "2026-01-05" \ --roast-date "2026-01-05" \
--amount 175 --amount 175
# Stow Coffee - Suke Quto (225g) # Stow Coffee - Suke Quto (225g)
./target/debug/brewlog add-bag \ ./target/debug/brewlog bag add \
--roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Suke Quto") | .id')" \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Suke Quto") | .id')" \
--roast-date "2026-01-30" \ --roast-date "2026-01-30" \
--amount 225 --amount 225
# Bows Coffee - Simbi (450g) # Bows Coffee - Simbi (450g)
./target/debug/brewlog add-bag \ ./target/debug/brewlog bag add \
--roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Simbi") | .id')" \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Simbi") | .id')" \
--roast-date "2026-01-14" \ --roast-date "2026-01-14" \
--amount 450 --amount 450
@ -496,26 +496,26 @@ fi
# ============================================================================ # ============================================================================
# Finish Gatomboya bag (oldest - Jan 5) # Finish Gatomboya bag (oldest - Jan 5)
./target/debug/brewlog update-bag \ ./target/debug/brewlog bag update \
--id "$(./target/debug/brewlog list-bags | jq -r '.[] | select(.roast_name=="Gatomboya") | .id')" \ --id "$(./target/debug/brewlog bag list | jq -r '.[] | select(.roast_name=="Gatomboya") | .id')" \
--closed true \ --closed true \
--finished-at "2026-01-20" --finished-at "2026-01-20"
# Finish Kochere bag (Jan 8) # Finish Kochere bag (Jan 8)
./target/debug/brewlog update-bag \ ./target/debug/brewlog bag update \
--id "$(./target/debug/brewlog list-bags | jq -r '.[] | select(.roast_name=="Kochere") | .id')" \ --id "$(./target/debug/brewlog bag list | jq -r '.[] | select(.roast_name=="Kochere") | .id')" \
--closed true \ --closed true \
--finished-at "2026-01-22" --finished-at "2026-01-22"
# Finish Daterra Sweet Collection bag (Jan 10) # Finish Daterra Sweet Collection bag (Jan 10)
./target/debug/brewlog update-bag \ ./target/debug/brewlog bag update \
--id "$(./target/debug/brewlog list-bags | jq -r '.[] | select(.roast_name=="Daterra Sweet Collection") | .id')" \ --id "$(./target/debug/brewlog bag list | jq -r '.[] | select(.roast_name=="Daterra Sweet Collection") | .id')" \
--closed true \ --closed true \
--finished-at "2026-01-25" --finished-at "2026-01-25"
# Finish Halo Beriti bag (Jan 12) # Finish Halo Beriti bag (Jan 12)
./target/debug/brewlog update-bag \ ./target/debug/brewlog bag update \
--id "$(./target/debug/brewlog list-bags | jq -r '.[] | select(.roast_name=="Halo Beriti") | .id')" \ --id "$(./target/debug/brewlog bag list | jq -r '.[] | select(.roast_name=="Halo Beriti") | .id')" \
--closed true \ --closed true \
--finished-at "2026-01-28" --finished-at "2026-01-28"
@ -524,32 +524,33 @@ fi
# ============================================================================ # ============================================================================
# Grinders # Grinders
./target/debug/brewlog add-gear \ ./target/debug/brewlog gear add \
--category "grinder" \ --category "grinder" \
--make "Comandante" \ --make "Comandante" \
--model "C40 MK4" --model "C40 MK4"
./target/debug/brewlog add-gear \ ./target/debug/brewlog gear add \
--category "grinder" \ --category "grinder" \
--make "1Zpresso" \ --make "1Zpresso" \
--model "J-Max" --model "J-Max"
# Brewers # Brewers
./target/debug/brewlog add-gear \ ./target/debug/brewlog gear add \
--category "brewer" \ --category "brewer" \
--make "Hario" \ --make "Hario" \
--model "V60 02" --model "V60 02"
./target/debug/brewlog add-gear \ ./target/debug/brewlog gear add \
--category "brewer" \ --category "brewer" \
--make "AeroPress" \ --make "AeroPress" \
--model "Original" --model "Original"
./target/debug/brewlog add-gear \ ./target/debug/brewlog gear add \
--category "brewer" \ --category "brewer" \
--make "Fellow" \ --make "Fellow" \
--model "Stagg XF" --model "Stagg XF"
echo
echo "Bootstrapped database" echo "Bootstrapped database"
echo echo
echo "Set token $BREWLOG_TOKEN to use the data added here." echo "Set token $BREWLOG_TOKEN to use the data added here."

View file

@ -21,43 +21,25 @@ async fn main() -> Result<()> {
match cli.command { match cli.command {
Commands::Serve(cmd) => run_server(cmd).await, Commands::Serve(cmd) => run_server(cmd).await,
command => { Commands::Roaster { command } => {
let client = BrewlogClient::from_base_url(&cli.api_url)?; let client = BrewlogClient::from_base_url(&cli.api_url)?;
match command { roasters::run(&client, command).await
// Tokens }
Commands::CreateToken(cmd) => tokens::create_token(&client, cmd).await, Commands::Roast { command } => {
Commands::ListTokens => tokens::list_tokens(&client).await, let client = BrewlogClient::from_base_url(&cli.api_url)?;
Commands::RevokeToken(cmd) => tokens::revoke_token(&client, cmd).await, roasts::run(&client, command).await
}
// Roasters Commands::Bag { command } => {
Commands::AddRoaster(cmd) => roasters::add_roaster(&client, cmd).await, let client = BrewlogClient::from_base_url(&cli.api_url)?;
Commands::ListRoasters => roasters::list_roasters(&client).await, bags::run(&client, command).await
Commands::GetRoaster(cmd) => roasters::get_roaster(&client, cmd).await, }
Commands::UpdateRoaster(cmd) => roasters::update_roaster(&client, cmd).await, Commands::Gear { command } => {
Commands::DeleteRoaster(cmd) => roasters::delete_roaster(&client, cmd).await, let client = BrewlogClient::from_base_url(&cli.api_url)?;
gear::run(&client, command).await
// Roasts }
Commands::AddRoast(cmd) => roasts::add_roast(&client, cmd).await, Commands::Token { command } => {
Commands::ListRoasts(cmd) => roasts::list_roasts(&client, cmd).await, let client = BrewlogClient::from_base_url(&cli.api_url)?;
Commands::GetRoast(cmd) => roasts::get_roast(&client, cmd).await, tokens::run(&client, command).await
Commands::DeleteRoast(cmd) => roasts::delete_roast(&client, cmd).await,
// Bags
Commands::AddBag(cmd) => bags::add_bag(&client, cmd).await,
Commands::ListBags(cmd) => bags::list_bags(&client, cmd).await,
Commands::GetBag(cmd) => bags::get_bag(&client, cmd).await,
Commands::UpdateBag(cmd) => bags::update_bag(&client, cmd).await,
Commands::DeleteBag(cmd) => bags::delete_bag(&client, cmd).await,
// Gear
Commands::AddGear(cmd) => gear::add_gear(&client, cmd).await,
Commands::ListGear(cmd) => gear::list_gear(&client, cmd).await,
Commands::GetGear(cmd) => gear::get_gear(&client, cmd).await,
Commands::UpdateGear(cmd) => gear::update_gear(&client, cmd).await,
Commands::DeleteGear(cmd) => gear::delete_gear(&client, cmd).await,
Commands::Serve(_) => unreachable!("serve command handled earlier"),
}
} }
} }
} }

View file

@ -1,11 +1,35 @@
use anyhow::Result; use anyhow::Result;
use clap::Args; use clap::{Args, Subcommand};
use super::macros::{define_delete_command, define_get_command}; use super::macros::{define_delete_command, define_get_command};
use super::print_json; use super::print_json;
use crate::domain::ids::{BagId, RoastId}; use crate::domain::ids::{BagId, RoastId};
use crate::infrastructure::client::BrewlogClient; use crate::infrastructure::client::BrewlogClient;
#[derive(Debug, Subcommand)]
pub enum BagCommands {
/// Add a new bag
Add(AddBagCommand),
/// List all bags
List(ListBagsCommand),
/// Get a bag by ID
Get(GetBagCommand),
/// Update a bag
Update(UpdateBagCommand),
/// Delete a bag
Delete(DeleteBagCommand),
}
pub async fn run(client: &BrewlogClient, cmd: BagCommands) -> Result<()> {
match cmd {
BagCommands::Add(c) => add_bag(client, c).await,
BagCommands::List(c) => list_bags(client, c).await,
BagCommands::Get(c) => get_bag(client, c).await,
BagCommands::Update(c) => update_bag(client, c).await,
BagCommands::Delete(c) => delete_bag(client, c).await,
}
}
#[derive(Debug, Args)] #[derive(Debug, Args)]
pub struct AddBagCommand { pub struct AddBagCommand {
#[arg(long)] #[arg(long)]

View file

@ -1,11 +1,35 @@
use anyhow::Result; use anyhow::Result;
use clap::Args; use clap::{Args, Subcommand};
use super::macros::{define_delete_command, define_get_command}; use super::macros::{define_delete_command, define_get_command};
use super::print_json; use super::print_json;
use crate::domain::ids::GearId; use crate::domain::ids::GearId;
use crate::infrastructure::client::BrewlogClient; use crate::infrastructure::client::BrewlogClient;
#[derive(Debug, Subcommand)]
pub enum GearCommands {
/// Add new gear
Add(AddGearCommand),
/// List all gear
List(ListGearCommand),
/// Get gear by ID
Get(GetGearCommand),
/// Update gear
Update(UpdateGearCommand),
/// Delete gear
Delete(DeleteGearCommand),
}
pub async fn run(client: &BrewlogClient, cmd: GearCommands) -> Result<()> {
match cmd {
GearCommands::Add(c) => add_gear(client, c).await,
GearCommands::List(c) => list_gear(client, c).await,
GearCommands::Get(c) => get_gear(client, c).await,
GearCommands::Update(c) => update_gear(client, c).await,
GearCommands::Delete(c) => delete_gear(client, c).await,
}
}
#[derive(Debug, Args)] #[derive(Debug, Args)]
pub struct AddGearCommand { pub struct AddGearCommand {
#[arg(long)] #[arg(long)]

View file

@ -7,12 +7,12 @@ pub mod tokens;
use std::net::SocketAddr; use std::net::SocketAddr;
use bags::{AddBagCommand, DeleteBagCommand, GetBagCommand, ListBagsCommand, UpdateBagCommand}; use bags::BagCommands;
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use gear::{AddGearCommand, DeleteGearCommand, GetGearCommand, ListGearCommand, UpdateGearCommand}; use gear::GearCommands;
use roasters::{AddRoasterCommand, DeleteRoasterCommand, GetRoasterCommand, UpdateRoasterCommand}; use roasters::RoasterCommands;
use roasts::{AddRoastCommand, DeleteRoastCommand, GetRoastCommand, ListRoastsCommand}; use roasts::RoastCommands;
use tokens::{CreateTokenCommand, RevokeTokenCommand}; use tokens::TokenCommands;
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(author, version, about = "Track coffee roasts, brews, and cups", long_about = None)] #[command(author, version, about = "Track coffee roasts, brews, and cups", long_about = None)]
@ -31,62 +31,38 @@ pub struct Cli {
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum Commands { pub enum Commands {
#[command(name = "serve")] /// Run the HTTP server
Serve(ServeCommand), Serve(ServeCommand),
// Tokens /// Manage roasters
#[command(name = "create-token")] Roaster {
CreateToken(CreateTokenCommand), #[command(subcommand)]
#[command(name = "list-tokens")] command: RoasterCommands,
ListTokens, },
#[command(name = "revoke-token")]
RevokeToken(RevokeTokenCommand),
// Roasters /// Manage roasts
#[command(name = "add-roaster")] Roast {
AddRoaster(AddRoasterCommand), #[command(subcommand)]
#[command(name = "list-roasters")] command: RoastCommands,
ListRoasters, },
#[command(name = "get-roaster")]
GetRoaster(GetRoasterCommand),
#[command(name = "update-roaster")]
UpdateRoaster(UpdateRoasterCommand),
#[command(name = "delete-roaster")]
DeleteRoaster(DeleteRoasterCommand),
// Roasts /// Manage bags
#[command(name = "add-roast")] Bag {
AddRoast(AddRoastCommand), #[command(subcommand)]
#[command(name = "list-roasts")] command: BagCommands,
ListRoasts(ListRoastsCommand), },
#[command(name = "get-roast")]
GetRoast(GetRoastCommand),
#[command(name = "delete-roast")]
DeleteRoast(DeleteRoastCommand),
// Bags /// Manage gear
#[command(name = "add-bag")] Gear {
AddBag(AddBagCommand), #[command(subcommand)]
#[command(name = "list-bags")] command: GearCommands,
ListBags(ListBagsCommand), },
#[command(name = "get-bag")]
GetBag(GetBagCommand),
#[command(name = "update-bag")]
UpdateBag(UpdateBagCommand),
#[command(name = "delete-bag")]
DeleteBag(DeleteBagCommand),
// Gear /// Manage API tokens
#[command(name = "add-gear")] Token {
AddGear(AddGearCommand), #[command(subcommand)]
#[command(name = "list-gear")] command: TokenCommands,
ListGear(ListGearCommand), },
#[command(name = "get-gear")]
GetGear(GetGearCommand),
#[command(name = "update-gear")]
UpdateGear(UpdateGearCommand),
#[command(name = "delete-gear")]
DeleteGear(DeleteGearCommand),
} }
#[derive(Debug, Args)] #[derive(Debug, Args)]

View file

@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::Result;
use clap::Args; use clap::{Args, Subcommand};
use super::macros::{define_delete_command, define_get_command}; use super::macros::{define_delete_command, define_get_command};
use super::print_json; use super::print_json;
@ -7,6 +7,30 @@ use crate::domain::ids::RoasterId;
use crate::domain::roasters::{NewRoaster, UpdateRoaster}; use crate::domain::roasters::{NewRoaster, UpdateRoaster};
use crate::infrastructure::client::BrewlogClient; use crate::infrastructure::client::BrewlogClient;
#[derive(Debug, Subcommand)]
pub enum RoasterCommands {
/// Add a new roaster
Add(AddRoasterCommand),
/// List all roasters
List,
/// Get a roaster by ID
Get(GetRoasterCommand),
/// Update a roaster
Update(UpdateRoasterCommand),
/// Delete a roaster
Delete(DeleteRoasterCommand),
}
pub async fn run(client: &BrewlogClient, cmd: RoasterCommands) -> Result<()> {
match cmd {
RoasterCommands::Add(c) => add_roaster(client, c).await,
RoasterCommands::List => list_roasters(client).await,
RoasterCommands::Get(c) => get_roaster(client, c).await,
RoasterCommands::Update(c) => update_roaster(client, c).await,
RoasterCommands::Delete(c) => delete_roaster(client, c).await,
}
}
#[derive(Debug, Args)] #[derive(Debug, Args)]
pub struct AddRoasterCommand { pub struct AddRoasterCommand {
#[arg(long)] #[arg(long)]

View file

@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::Result;
use clap::Args; use clap::{Args, Subcommand};
use super::macros::{define_delete_command, define_get_command}; use super::macros::{define_delete_command, define_get_command};
use super::print_json; use super::print_json;
@ -7,6 +7,27 @@ use crate::domain::ids::{RoastId, RoasterId};
use crate::domain::roasts::NewRoast; use crate::domain::roasts::NewRoast;
use crate::infrastructure::client::BrewlogClient; 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),
/// 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::Delete(c) => delete_roast(client, c).await,
}
}
#[derive(Debug, Args)] #[derive(Debug, Args)]
pub struct AddRoastCommand { pub struct AddRoastCommand {
#[arg(long)] #[arg(long)]

View file

@ -1,11 +1,29 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use clap::Args; use clap::{Args, Subcommand};
use std::io::{self, Write}; use std::io::{self, Write};
use super::print_json; use super::print_json;
use crate::domain::ids::TokenId; use crate::domain::ids::TokenId;
use crate::infrastructure::client::BrewlogClient; use crate::infrastructure::client::BrewlogClient;
#[derive(Debug, Subcommand)]
pub enum TokenCommands {
/// Create a new API token
Create(CreateTokenCommand),
/// List all tokens
List,
/// Revoke a token
Revoke(RevokeTokenCommand),
}
pub async fn run(client: &BrewlogClient, cmd: TokenCommands) -> Result<()> {
match cmd {
TokenCommands::Create(c) => create_token(client, c).await,
TokenCommands::List => list_tokens(client).await,
TokenCommands::Revoke(c) => revoke_token(client, c).await,
}
}
#[derive(Debug, Args)] #[derive(Debug, Args)]
pub struct CreateTokenCommand { pub struct CreateTokenCommand {
/// A descriptive name for this token /// A descriptive name for this token

View file

@ -5,9 +5,12 @@ use serde_json::Value;
fn test_add_bag_requires_authentication() { fn test_add_bag_requires_authentication() {
let _ = server_info(); let _ = server_info();
let output = run_brewlog(&["add-bag", "--roast-id", "123", "--amount", "250.0"], &[]); let output = run_brewlog(
&["bag", "add", "--roast-id", "123", "--amount", "250.0"],
&[],
);
assert!(!output.status.success(), "add-bag without auth should fail"); assert!(!output.status.success(), "bag add without auth should fail");
} }
#[test] #[test]
@ -21,7 +24,8 @@ fn test_add_bag_with_authentication() {
// Test: Add Bag // Test: Add Bag
let output = run_brewlog( let output = run_brewlog(
&[ &[
"add-bag", "bag",
"add",
"--roast-id", "--roast-id",
&roast_id, &roast_id,
"--amount", "--amount",
@ -44,11 +48,14 @@ fn test_add_bag_with_authentication() {
fn test_update_bag_requires_authentication() { fn test_update_bag_requires_authentication() {
let _ = server_info(); let _ = server_info();
let output = run_brewlog(&["update-bag", "--id", "123", "--remaining", "100.0"], &[]); let output = run_brewlog(
&["bag", "update", "--id", "123", "--remaining", "100.0"],
&[],
);
assert!( assert!(
!output.status.success(), !output.status.success(),
"update-bag without auth should fail" "bag update without auth should fail"
); );
} }
@ -61,7 +68,7 @@ fn test_update_bag_with_authentication() {
let roast_id = create_roast(&roaster_id, "Bag Update Roast", &token); let roast_id = create_roast(&roaster_id, "Bag Update Roast", &token);
let bag_output = run_brewlog( let bag_output = run_brewlog(
&["add-bag", "--roast-id", &roast_id, "--amount", "250.0"], &["bag", "add", "--roast-id", &roast_id, "--amount", "250.0"],
&[("BREWLOG_TOKEN", &token)], &[("BREWLOG_TOKEN", &token)],
); );
let bag: Value = serde_json::from_slice(&bag_output.stdout).unwrap(); let bag: Value = serde_json::from_slice(&bag_output.stdout).unwrap();
@ -70,7 +77,8 @@ fn test_update_bag_with_authentication() {
// Test: Update Bag // Test: Update Bag
let output = run_brewlog( let output = run_brewlog(
&[ &[
"update-bag", "bag",
"update",
"--id", "--id",
&bag_id, &bag_id,
"--remaining", "--remaining",
@ -92,7 +100,7 @@ fn test_update_bag_with_authentication() {
fn test_list_bags_works_without_authentication() { fn test_list_bags_works_without_authentication() {
let _ = server_info(); let _ = server_info();
// Listing without roast_id might return all or empty, but should succeed (200 OK) // Listing without roast_id might return all or empty, but should succeed (200 OK)
let output = run_brewlog(&["list-bags"], &[]); let output = run_brewlog(&["bag", "list"], &[]);
assert!(output.status.success()); assert!(output.status.success());
} }
@ -105,7 +113,7 @@ fn test_list_bags_shows_added_bag() {
let roast_id = create_roast(&roaster_id, "Bag List Roast", &token); let roast_id = create_roast(&roaster_id, "Bag List Roast", &token);
let bag_output = run_brewlog( let bag_output = run_brewlog(
&["add-bag", "--roast-id", &roast_id, "--amount", "250.0"], &["bag", "add", "--roast-id", &roast_id, "--amount", "250.0"],
&[("BREWLOG_TOKEN", &token)], &[("BREWLOG_TOKEN", &token)],
); );
let bag: Value = serde_json::from_slice(&bag_output.stdout).unwrap(); let bag: Value = serde_json::from_slice(&bag_output.stdout).unwrap();
@ -113,7 +121,7 @@ fn test_list_bags_shows_added_bag() {
// Test: List Bags (Authenticated) // Test: List Bags (Authenticated)
let output = run_brewlog( let output = run_brewlog(
&["list-bags", "--roast-id", &roast_id], &["bag", "list", "--roast-id", &roast_id],
&[("BREWLOG_TOKEN", &token)], &[("BREWLOG_TOKEN", &token)],
); );
@ -133,14 +141,14 @@ fn test_list_bags_without_roast_id_shows_all_bags() {
let roast_id = create_roast(&roaster_id, "Bag List All Roast", &token); let roast_id = create_roast(&roaster_id, "Bag List All Roast", &token);
let bag_output = run_brewlog( let bag_output = run_brewlog(
&["add-bag", "--roast-id", &roast_id, "--amount", "250.0"], &["bag", "add", "--roast-id", &roast_id, "--amount", "250.0"],
&[("BREWLOG_TOKEN", &token)], &[("BREWLOG_TOKEN", &token)],
); );
let bag: Value = serde_json::from_slice(&bag_output.stdout).unwrap(); let bag: Value = serde_json::from_slice(&bag_output.stdout).unwrap();
let bag_id = bag["id"].as_i64().unwrap(); let bag_id = bag["id"].as_i64().unwrap();
// Test: List Bags (Authenticated) // Test: List Bags (Authenticated)
let output = run_brewlog(&["list-bags"], &[("BREWLOG_TOKEN", &token)]); let output = run_brewlog(&["bag", "list"], &[("BREWLOG_TOKEN", &token)]);
assert!(output.status.success()); assert!(output.status.success());
let bags: Value = serde_json::from_slice(&output.stdout).unwrap(); let bags: Value = serde_json::from_slice(&output.stdout).unwrap();
@ -159,7 +167,7 @@ fn test_list_bags_shows_open_and_closed_bags() {
// Create Open Bag // Create Open Bag
let open_bag_output = run_brewlog( let open_bag_output = run_brewlog(
&["add-bag", "--roast-id", &roast_id, "--amount", "250.0"], &["bag", "add", "--roast-id", &roast_id, "--amount", "250.0"],
&[("BREWLOG_TOKEN", &token)], &[("BREWLOG_TOKEN", &token)],
); );
let open_bag: Value = serde_json::from_slice(&open_bag_output.stdout).unwrap(); let open_bag: Value = serde_json::from_slice(&open_bag_output.stdout).unwrap();
@ -167,7 +175,7 @@ fn test_list_bags_shows_open_and_closed_bags() {
// Create Closed Bag // Create Closed Bag
let closed_bag_output = run_brewlog( let closed_bag_output = run_brewlog(
&["add-bag", "--roast-id", &roast_id, "--amount", "250.0"], &["bag", "add", "--roast-id", &roast_id, "--amount", "250.0"],
&[("BREWLOG_TOKEN", &token)], &[("BREWLOG_TOKEN", &token)],
); );
let closed_bag: Value = serde_json::from_slice(&closed_bag_output.stdout).unwrap(); let closed_bag: Value = serde_json::from_slice(&closed_bag_output.stdout).unwrap();
@ -176,7 +184,8 @@ fn test_list_bags_shows_open_and_closed_bags() {
// Close the second bag // Close the second bag
let _ = run_brewlog( let _ = run_brewlog(
&[ &[
"update-bag", "bag",
"update",
"--id", "--id",
&closed_bag_id.to_string(), &closed_bag_id.to_string(),
"--closed", "--closed",
@ -186,7 +195,7 @@ fn test_list_bags_shows_open_and_closed_bags() {
); );
// Test: List Bags (Authenticated) // Test: List Bags (Authenticated)
let output = run_brewlog(&["list-bags"], &[("BREWLOG_TOKEN", &token)]); let output = run_brewlog(&["bag", "list"], &[("BREWLOG_TOKEN", &token)]);
assert!(output.status.success()); assert!(output.status.success());
let bags: Value = serde_json::from_slice(&output.stdout).unwrap(); let bags: Value = serde_json::from_slice(&output.stdout).unwrap();
@ -210,7 +219,7 @@ fn test_list_bags_shows_open_and_closed_bags() {
#[test] #[test]
fn test_delete_bag_requires_authentication() { fn test_delete_bag_requires_authentication() {
let _ = server_info(); let _ = server_info();
let output = run_brewlog(&["delete-bag", "--id", "123"], &[]); let output = run_brewlog(&["bag", "delete", "--id", "123"], &[]);
assert!(!output.status.success()); assert!(!output.status.success());
} }
@ -223,7 +232,7 @@ fn test_delete_bag_with_authentication() {
let roast_id = create_roast(&roaster_id, "Bag Delete Roast", &token); let roast_id = create_roast(&roaster_id, "Bag Delete Roast", &token);
let bag_output = run_brewlog( let bag_output = run_brewlog(
&["add-bag", "--roast-id", &roast_id, "--amount", "250.0"], &["bag", "add", "--roast-id", &roast_id, "--amount", "250.0"],
&[("BREWLOG_TOKEN", &token)], &[("BREWLOG_TOKEN", &token)],
); );
let bag: Value = serde_json::from_slice(&bag_output.stdout).unwrap(); let bag: Value = serde_json::from_slice(&bag_output.stdout).unwrap();
@ -231,12 +240,12 @@ fn test_delete_bag_with_authentication() {
// Test: Delete Bag // Test: Delete Bag
let output = run_brewlog( let output = run_brewlog(
&["delete-bag", "--id", &bag_id], &["bag", "delete", "--id", &bag_id],
&[("BREWLOG_TOKEN", &token)], &[("BREWLOG_TOKEN", &token)],
); );
assert!(output.status.success()); assert!(output.status.success());
// Verify deletion // Verify deletion
let get_output = run_brewlog(&["get-bag", "--id", &bag_id], &[]); let get_output = run_brewlog(&["bag", "get", "--id", &bag_id], &[]);
assert!(!get_output.status.success()); assert!(!get_output.status.success());
} }

View file

@ -7,7 +7,8 @@ fn test_add_gear_requires_authentication() {
let output = run_brewlog( let output = run_brewlog(
&[ &[
"add-gear", "gear",
"add",
"--category", "--category",
"grinder", "grinder",
"--make", "--make",
@ -20,7 +21,7 @@ fn test_add_gear_requires_authentication() {
assert!( assert!(
!output.status.success(), !output.status.success(),
"add-gear without auth should fail" "gear add without auth should fail"
); );
} }
@ -30,7 +31,8 @@ fn test_add_gear_with_authentication() {
let output = run_brewlog( let output = run_brewlog(
&[ &[
"add-gear", "gear",
"add",
"--category", "--category",
"grinder", "grinder",
"--make", "--make",
@ -54,11 +56,11 @@ fn test_add_gear_with_authentication() {
fn test_list_gear_works_without_authentication() { fn test_list_gear_works_without_authentication() {
let _ = server_info(); let _ = server_info();
let output = run_brewlog(&["list-gear"], &[]); let output = run_brewlog(&["gear", "list"], &[]);
assert!( assert!(
output.status.success(), output.status.success(),
"list-gear should work without auth" "gear list should work without auth"
); );
} }
@ -69,7 +71,8 @@ fn test_list_gear_shows_added_gear() {
// Add gear // Add gear
let add_output = run_brewlog( let add_output = run_brewlog(
&[ &[
"add-gear", "gear",
"add",
"--category", "--category",
"grinder", "grinder",
"--make", "--make",
@ -82,7 +85,7 @@ fn test_list_gear_shows_added_gear() {
assert!(add_output.status.success()); assert!(add_output.status.success());
// List gear // List gear
let list_output = run_brewlog(&["list-gear"], &[]); let list_output = run_brewlog(&["gear", "list"], &[]);
assert!(list_output.status.success()); assert!(list_output.status.success());
let gear_list: Value = serde_json::from_slice(&list_output.stdout).unwrap(); let gear_list: Value = serde_json::from_slice(&list_output.stdout).unwrap();
@ -103,7 +106,8 @@ fn test_list_gear_filtered_by_category() {
// Add grinder // Add grinder
run_brewlog( run_brewlog(
&[ &[
"add-gear", "gear",
"add",
"--category", "--category",
"grinder", "grinder",
"--make", "--make",
@ -117,7 +121,8 @@ fn test_list_gear_filtered_by_category() {
// Add brewer // Add brewer
run_brewlog( run_brewlog(
&[ &[
"add-gear", "gear",
"add",
"--category", "--category",
"brewer", "brewer",
"--make", "--make",
@ -129,7 +134,7 @@ fn test_list_gear_filtered_by_category() {
); );
// List only grinders // List only grinders
let output = run_brewlog(&["list-gear", "--category", "grinder"], &[]); let output = run_brewlog(&["gear", "list", "--category", "grinder"], &[]);
assert!(output.status.success()); assert!(output.status.success());
let gear_list: Value = serde_json::from_slice(&output.stdout).unwrap(); let gear_list: Value = serde_json::from_slice(&output.stdout).unwrap();
@ -151,7 +156,8 @@ fn test_get_gear_by_id() {
// Add gear // Add gear
let add_output = run_brewlog( let add_output = run_brewlog(
&[ &[
"add-gear", "gear",
"add",
"--category", "--category",
"grinder", "grinder",
"--make", "--make",
@ -165,7 +171,7 @@ fn test_get_gear_by_id() {
let gear_id = gear["id"].as_i64().unwrap().to_string(); let gear_id = gear["id"].as_i64().unwrap().to_string();
// Get gear by ID // Get gear by ID
let output = run_brewlog(&["get-gear", "--id", &gear_id], &[]); let output = run_brewlog(&["gear", "get", "--id", &gear_id], &[]);
assert!(output.status.success()); assert!(output.status.success());
let retrieved_gear: Value = serde_json::from_slice(&output.stdout).unwrap(); let retrieved_gear: Value = serde_json::from_slice(&output.stdout).unwrap();
@ -177,11 +183,11 @@ fn test_get_gear_by_id() {
fn test_update_gear_requires_authentication() { fn test_update_gear_requires_authentication() {
let _ = server_info(); let _ = server_info();
let output = run_brewlog(&["update-gear", "--id", "123", "--make", "Updated"], &[]); let output = run_brewlog(&["gear", "update", "--id", "123", "--make", "Updated"], &[]);
assert!( assert!(
!output.status.success(), !output.status.success(),
"update-gear without auth should fail" "gear update without auth should fail"
); );
} }
@ -192,7 +198,8 @@ fn test_update_gear_with_authentication() {
// Add gear // Add gear
let add_output = run_brewlog( let add_output = run_brewlog(
&[ &[
"add-gear", "gear",
"add",
"--category", "--category",
"grinder", "grinder",
"--make", "--make",
@ -207,7 +214,7 @@ fn test_update_gear_with_authentication() {
// Update gear // Update gear
let output = run_brewlog( let output = run_brewlog(
&["update-gear", "--id", &gear_id, "--model", "Mini II"], &["gear", "update", "--id", &gear_id, "--model", "Mini II"],
&[("BREWLOG_TOKEN", &token)], &[("BREWLOG_TOKEN", &token)],
); );
@ -221,11 +228,11 @@ fn test_update_gear_with_authentication() {
fn test_delete_gear_requires_authentication() { fn test_delete_gear_requires_authentication() {
let _ = server_info(); let _ = server_info();
let output = run_brewlog(&["delete-gear", "--id", "123"], &[]); let output = run_brewlog(&["gear", "delete", "--id", "123"], &[]);
assert!( assert!(
!output.status.success(), !output.status.success(),
"delete-gear without auth should fail" "gear delete without auth should fail"
); );
} }
@ -236,7 +243,8 @@ fn test_delete_gear_with_authentication() {
// Add gear // Add gear
let add_output = run_brewlog( let add_output = run_brewlog(
&[ &[
"add-gear", "gear",
"add",
"--category", "--category",
"grinder", "grinder",
"--make", "--make",
@ -251,12 +259,12 @@ fn test_delete_gear_with_authentication() {
// Delete gear // Delete gear
let delete_output = run_brewlog( let delete_output = run_brewlog(
&["delete-gear", "--id", &gear_id], &["gear", "delete", "--id", &gear_id],
&[("BREWLOG_TOKEN", &token)], &[("BREWLOG_TOKEN", &token)],
); );
assert!(delete_output.status.success()); assert!(delete_output.status.success());
// Verify deletion - get should fail // Verify deletion - get should fail
let get_output = run_brewlog(&["get-gear", "--id", &gear_id], &[]); let get_output = run_brewlog(&["gear", "get", "--id", &gear_id], &[]);
assert!(!get_output.status.success()); assert!(!get_output.status.success());
} }

View file

@ -119,7 +119,8 @@ pub fn create_token(name: &str) -> String {
let output = run_brewlog( let output = run_brewlog(
&[ &[
"create-token", "token",
"create",
"--name", "--name",
name, name,
"--username", "--username",
@ -165,7 +166,7 @@ pub fn run_brewlog(args: &[&str], env: &[(&str, &str)]) -> std::process::Output
/// Helper to create a roaster and return its ID /// Helper to create a roaster and return its ID
pub fn create_roaster(name: &str, token: &str) -> String { pub fn create_roaster(name: &str, token: &str) -> String {
let output = run_brewlog( let output = run_brewlog(
&["add-roaster", "--name", name, "--country", "UK"], &["roaster", "add", "--name", name, "--country", "UK"],
&[("BREWLOG_TOKEN", token)], &[("BREWLOG_TOKEN", token)],
); );
@ -189,7 +190,8 @@ pub fn create_roaster(name: &str, token: &str) -> String {
pub fn create_roast(roaster_id: &str, name: &str, token: &str) -> String { pub fn create_roast(roaster_id: &str, name: &str, token: &str) -> String {
let output = run_brewlog( let output = run_brewlog(
&[ &[
"add-roast", "roast",
"add",
"--roaster-id", "--roaster-id",
roaster_id, roaster_id,
"--name", "--name",

View file

@ -6,13 +6,20 @@ fn test_add_roaster_requires_authentication() {
let _ = server_info(); // Ensure server is started let _ = server_info(); // Ensure server is started
let output = run_brewlog( let output = run_brewlog(
&["add-roaster", "--name", "Test Roasters", "--country", "UK"], &[
"roaster",
"add",
"--name",
"Test Roasters",
"--country",
"UK",
],
&[], &[],
); );
assert!( assert!(
!output.status.success(), !output.status.success(),
"add-roaster without auth should fail" "roaster add without auth should fail"
); );
} }
@ -21,13 +28,20 @@ fn test_add_roaster_with_authentication() {
let token = create_token("test-add-roaster"); let token = create_token("test-add-roaster");
let output = run_brewlog( let output = run_brewlog(
&["add-roaster", "--name", "Test Roasters", "--country", "UK"], &[
"roaster",
"add",
"--name",
"Test Roasters",
"--country",
"UK",
],
&[("BREWLOG_TOKEN", &token)], &[("BREWLOG_TOKEN", &token)],
); );
assert!( assert!(
output.status.success(), output.status.success(),
"add-roaster with auth should succeed: {}", "roaster add with auth should succeed: {}",
String::from_utf8_lossy(&output.stderr) String::from_utf8_lossy(&output.stderr)
); );
@ -44,11 +58,11 @@ fn test_add_roaster_with_authentication() {
fn test_list_roasters_works_without_authentication() { fn test_list_roasters_works_without_authentication() {
let _ = server_info(); let _ = server_info();
let output = run_brewlog(&["list-roasters"], &[]); let output = run_brewlog(&["roaster", "list"], &[]);
assert!( assert!(
output.status.success(), output.status.success(),
"list-roasters should work without auth" "roaster list should work without auth"
); );
let stdout = String::from_utf8_lossy(&output.stdout); let stdout = String::from_utf8_lossy(&output.stdout);
@ -65,7 +79,7 @@ fn test_list_roasters_shows_added_roaster() {
let roaster_id = create_roaster("Example Roasters", &token); let roaster_id = create_roaster("Example Roasters", &token);
// List roasters // List roasters
let list_output = run_brewlog(&["list-roasters"], &[]); let list_output = run_brewlog(&["roaster", "list"], &[]);
assert!(list_output.status.success()); assert!(list_output.status.success());
@ -87,11 +101,11 @@ fn test_list_roasters_shows_added_roaster() {
fn test_delete_roaster_requires_authentication() { fn test_delete_roaster_requires_authentication() {
let _ = server_info(); let _ = server_info();
let output = run_brewlog(&["delete-roaster", "--id", "some-id"], &[]); let output = run_brewlog(&["roaster", "delete", "--id", "some-id"], &[]);
assert!( assert!(
!output.status.success(), !output.status.success(),
"delete-roaster without auth should fail" "roaster delete without auth should fail"
); );
} }
@ -101,7 +115,8 @@ fn test_update_roaster_requires_authentication() {
let output = run_brewlog( let output = run_brewlog(
&[ &[
"update-roaster", "roaster",
"update",
"--id", "--id",
"some-id", "some-id",
"--name", "--name",
@ -112,6 +127,6 @@ fn test_update_roaster_requires_authentication() {
assert!( assert!(
!output.status.success(), !output.status.success(),
"update-roaster without auth should fail" "roaster update without auth should fail"
); );
} }

View file

@ -7,7 +7,8 @@ fn test_add_roast_requires_authentication() {
let output = run_brewlog( let output = run_brewlog(
&[ &[
"add-roast", "roast",
"add",
"--roaster-id", "--roaster-id",
"some-id", "some-id",
"--name", "--name",
@ -26,7 +27,7 @@ fn test_add_roast_requires_authentication() {
assert!( assert!(
!output.status.success(), !output.status.success(),
"add-roast without auth should fail" "roast add without auth should fail"
); );
} }
@ -40,7 +41,8 @@ fn test_add_roast_with_authentication() {
// Now add a roast // Now add a roast
let output = run_brewlog( let output = run_brewlog(
&[ &[
"add-roast", "roast",
"add",
"--roaster-id", "--roaster-id",
&roaster_id, &roaster_id,
"--name", "--name",
@ -61,7 +63,7 @@ fn test_add_roast_with_authentication() {
assert!( assert!(
output.status.success(), output.status.success(),
"add-roast with auth should succeed: {}", "roast add with auth should succeed: {}",
String::from_utf8_lossy(&output.stderr) String::from_utf8_lossy(&output.stderr)
); );
@ -80,11 +82,11 @@ fn test_add_roast_with_authentication() {
fn test_list_roasts_works_without_authentication() { fn test_list_roasts_works_without_authentication() {
let _ = server_info(); let _ = server_info();
let output = run_brewlog(&["list-roasts"], &[]); let output = run_brewlog(&["roast", "list"], &[]);
assert!( assert!(
output.status.success(), output.status.success(),
"list-roasts should work without auth" "roast list should work without auth"
); );
let stdout = String::from_utf8_lossy(&output.stdout); let stdout = String::from_utf8_lossy(&output.stdout);
@ -103,7 +105,8 @@ fn test_list_roasts_shows_added_roast() {
// Add a roast // Add a roast
let add_output = run_brewlog( let add_output = run_brewlog(
&[ &[
"add-roast", "roast",
"add",
"--roaster-id", "--roaster-id",
&roaster_id, &roaster_id,
"--name", "--name",
@ -131,7 +134,7 @@ fn test_list_roasts_shows_added_roast() {
.expect("roast id should be numeric"); .expect("roast id should be numeric");
// List roasts // List roasts
let list_output = run_brewlog(&["list-roasts"], &[]); let list_output = run_brewlog(&["roast", "list"], &[]);
assert!(list_output.status.success()); assert!(list_output.status.success());
@ -157,10 +160,10 @@ fn test_list_roasts_shows_added_roast() {
fn test_delete_roast_requires_authentication() { fn test_delete_roast_requires_authentication() {
let _ = server_info(); let _ = server_info();
let output = run_brewlog(&["delete-roast", "--id", "some-id"], &[]); let output = run_brewlog(&["roast", "delete", "--id", "some-id"], &[]);
assert!( assert!(
!output.status.success(), !output.status.success(),
"delete-roast without auth should fail" "roast delete without auth should fail"
); );
} }

View file

@ -4,11 +4,11 @@ use crate::helpers::{create_token, run_brewlog, server_info};
fn test_list_tokens_requires_authentication() { fn test_list_tokens_requires_authentication() {
let _ = server_info(); let _ = server_info();
let output = run_brewlog(&["list-tokens"], &[]); let output = run_brewlog(&["token", "list"], &[]);
assert!( assert!(
!output.status.success(), !output.status.success(),
"list-tokens without auth should fail" "token list without auth should fail"
); );
} }
@ -16,11 +16,11 @@ fn test_list_tokens_requires_authentication() {
fn test_list_tokens_with_authentication() { fn test_list_tokens_with_authentication() {
let token = create_token("test-list-tokens"); let token = create_token("test-list-tokens");
let output = run_brewlog(&["list-tokens"], &[("BREWLOG_TOKEN", &token)]); let output = run_brewlog(&["token", "list"], &[("BREWLOG_TOKEN", &token)]);
assert!( assert!(
output.status.success(), output.status.success(),
"list-tokens with auth should succeed" "token list with auth should succeed"
); );
let stdout = String::from_utf8_lossy(&output.stdout); let stdout = String::from_utf8_lossy(&output.stdout);
@ -34,11 +34,11 @@ fn test_list_tokens_with_authentication() {
fn test_revoke_token_requires_authentication() { fn test_revoke_token_requires_authentication() {
let _ = server_info(); let _ = server_info();
let output = run_brewlog(&["revoke-token", "--id", "1"], &[]); let output = run_brewlog(&["token", "revoke", "--id", "1"], &[]);
assert!( assert!(
!output.status.success(), !output.status.success(),
"revoke-token without auth should fail" "token revoke without auth should fail"
); );
} }
@ -47,7 +47,7 @@ fn test_revoke_token_with_authentication() {
let token = create_token("test-revoke-token"); let token = create_token("test-revoke-token");
// List tokens to get the ID // List tokens to get the ID
let list_output = run_brewlog(&["list-tokens"], &[("BREWLOG_TOKEN", &token)]); let list_output = run_brewlog(&["token", "list"], &[("BREWLOG_TOKEN", &token)]);
assert!(list_output.status.success()); assert!(list_output.status.success());
let list_stdout = String::from_utf8_lossy(&list_output.stdout); let list_stdout = String::from_utf8_lossy(&list_output.stdout);
@ -66,7 +66,7 @@ fn test_revoke_token_with_authentication() {
.expect("Token should have ID"); .expect("Token should have ID");
let revoke_output = run_brewlog( let revoke_output = run_brewlog(
&["revoke-token", "--id", &token_id.to_string()], &["token", "revoke", "--id", &token_id.to_string()],
&[("BREWLOG_TOKEN", &token)], &[("BREWLOG_TOKEN", &token)],
); );
@ -85,7 +85,7 @@ fn test_revoked_token_cannot_be_used() {
let admin_token = create_token("test-admin-token"); let admin_token = create_token("test-admin-token");
// List tokens to get the ID of the token we want to revoke // List tokens to get the ID of the token we want to revoke
let list_output = run_brewlog(&["list-tokens"], &[("BREWLOG_TOKEN", &admin_token)]); let list_output = run_brewlog(&["token", "list"], &[("BREWLOG_TOKEN", &admin_token)]);
assert!(list_output.status.success()); assert!(list_output.status.success());
let list_stdout = String::from_utf8_lossy(&list_output.stdout); let list_stdout = String::from_utf8_lossy(&list_output.stdout);
@ -105,7 +105,7 @@ fn test_revoked_token_cannot_be_used() {
// Revoke the token // Revoke the token
let revoke_output = run_brewlog( let revoke_output = run_brewlog(
&["revoke-token", "--id", &token_id.to_string()], &["token", "revoke", "--id", &token_id.to_string()],
&[("BREWLOG_TOKEN", &admin_token)], &[("BREWLOG_TOKEN", &admin_token)],
); );
assert!( assert!(
@ -115,7 +115,7 @@ fn test_revoked_token_cannot_be_used() {
// Try to use the revoked token - it should fail // Try to use the revoked token - it should fail
let list_with_revoked_output = let list_with_revoked_output =
run_brewlog(&["list-tokens"], &[("BREWLOG_TOKEN", &token_to_revoke)]); run_brewlog(&["token", "list"], &[("BREWLOG_TOKEN", &token_to_revoke)]);
assert!( assert!(
!list_with_revoked_output.status.success(), !list_with_revoked_output.status.success(),