Commit graph

83 commits

Author SHA1 Message Date
Jon Seager
1fa88d054c
feat(roasts): consolidate table columns and add tasting notes search
- Combine Roast/Roaster into single column with roaster as subtext on desktop
- Combine Origin/Producer into single column with producer as subtext on desktop
- Add Roaster and Producer as separate mobile-only fields for card layout
- Replace tasting notes pill badges with comma-separated list
- Rename "Notes" column to "Tasting Notes" with right-aligned mobile text
- Include tasting_notes in repository search filter columns
2026-02-03 12:51:59 +00:00
Jon Seager
2edf102b41
feat(search): add search to ListQuery, ListNavigator, and route handlers
Add q parameter to ListQuery with into_request_and_search(). Extend
ListNavigator with search state, search_value(), has_search(), path(),
and search_query_base() for template URL generation. Thread search
through all route handlers and update backup tests.
2026-02-03 12:51:34 +00:00
Jon Seager
1e151123a0
feat(search): implement search filtering in repository SQL queries
Wire SearchFilter into each repository's list method with entity-specific
searchable columns: roasters (name, country, city), roasts (name, roaster,
origin, producer), bags (roaster, roast), brews (roast, roaster),
gear (make, model). Timeline passes None.
2026-02-03 12:51:26 +00:00
Jon Seager
f616958e3c
feat(search): add search parameter to repository traits
Add `search: Option<&str>` to list methods on RoasterRepository,
RoastRepository, BagRepository, GearRepository, and BrewRepository.
Default trait methods pass None for backwards compatibility.
2026-02-03 12:51:19 +00:00
Jon Seager
18d39d13ef
feat(search): add SearchFilter and DatabaseDriver to pagination infrastructure
Add SearchFilter struct for LIKE-based search with dynamic column
matching. Add DatabaseDriver type alias to support QueryBuilder
generics across SQLite and Postgres feature flags.
2026-02-03 12:51:08 +00:00
Jon Seager
2ac61345ec
feat(backup): add CLI backup and restore commands
- Add BackupData struct and BackupService with raw SQL export/import
- Restore uses raw inserts to bypass brew deductions and timeline creation
- Restore requires an empty database, inserts in FK dependency order
- Add comprehensive e2e test verifying full round-trip fidelity
2026-02-03 12:50:41 +00:00
Jon Seager
8b77eb90eb
feat(brews): add filter paper to routes, views, CLI, and templates
- Add filter paper dropdown to brew form and gear category form
- Add custom deserializer for optional GearId from HTML forms
- Load filter papers in brews_page handler
- Show filter paper as subtext under brewer in brew list
- Add filter paper to timeline event details and brew-again forms
- Add --filter-paper-id flag to CLI brew add command
2026-02-03 10:14:22 +00:00
Jon Seager
d636f6c246
feat(brews): support filter paper in repository and HTTP client
- Add filter_paper_id to SQL queries (SELECT, INSERT, LEFT JOIN)
- Add filter_paper_name via LEFT JOIN on gear table
- Update BrewRecord and BrewWithDetailsRecord structs
- Add filter_paper_id parameter to HTTP client create method
2026-02-03 10:14:00 +00:00
Jon Seager
0b967cbb14
feat(gear): add filter paper category and optional brew association
- Add migration to extend gear CHECK constraint with 'filter_paper'
  and add nullable filter_paper_id column to brews
- Add FilterPaper variant to GearCategory enum
- Add filter_paper_id to Brew, NewBrew, and BrewWithDetails
- Add filter_paper_id to TimelineBrewData
2026-02-03 10:13:35 +00:00
Jon Seager
80849f2bf5
refactor(brews): extract brew timeline event builder into helper
Move timeline event construction out of create_brew to keep the
handler under clippy's too_many_lines limit.
2026-02-03 08:56:29 +00:00
Jon Seager
949f65ac16
refactor(timeline): denormalize data and simplify frontend
Database:
- Add slug, roaster_slug, brew_data_json columns to timeline_events
- Migration backfills existing data from related tables
- Remove 9-way LEFT JOIN from list query, read directly from columns

Frontend:
- Use CSS :nth-of-type(odd/even) for alternating timeline layout
- Remove JavaScript class manipulation when appending month events
- Simplify infinite scroll month-merging logic

This eliminates query-time JOINs across 5 tables and ~20 lines of
client-side JavaScript for pattern maintenance.
2026-02-03 08:53:29 +00:00
Jon Seager
267ef2bf17
feat(timeline): remove sidebar nav, include sticky headers on scroll
- Simplify timeline layout to single-column without sidebar
- Update tests to explicitly pass page_size for pagination testing
- Include sticky headers and a neater alternating, side-by-side
  timeline design
2026-02-02 21:18:20 +00:00
Jon Seager
b12ad2bf6d
feat(timeline): add "brew again" button to timeline
- Add TimelineBrewData struct to domain layer to carry raw brew
  parameters needed for repeating a brew
- Extend timeline SQL query to LEFT JOIN with brews table and fetch
  brew data (bag_id, grinder_id, brewer_id, coffee_weight, etc.)
- Add brew_data field to TimelineEventView for template access
- Add is_authenticated to TimelineChunkTemplate for auth-aware chunks
- Add "brew again" button to timeline month partial, visible only for
  authenticated users on brew events
- Server returns reload script when brew is created from timeline page,
  using Datastar's fragment patching to append script to body
2026-02-02 20:14:51 +00:00
Jon Seager
9402d98bb6
fix(brews): improve table layout and add "brew again" feature
- Combine weight/water/ratio into single "Recipe" column
- Reorder columns: Coffee, Grind, Recipe, Temp, Brewer, Date, Actions
- Style actions column with icon buttons (consistent with roasts)
- Add "brew again" button to repeat a brew with same parameters
- Use data-model for form inputs (fixes Datastar binding error)
- Add raw values to BrewView for repeat brew functionality
2026-02-02 19:55:18 +00:00
Jon Seager
c4357f2698
feat(brews): add web views and templates
- Add BrewView, BagOptionView, GearOptionView
- Add brews page with form and +/- adjustment buttons
- Add brew list partial with sortable columns
- Add Brews link to navigation
2026-02-02 19:31:46 +00:00
Jon Seager
35d64eaed2
feat(brews): add CLI commands
- Add brew add/list/get/delete subcommands
- Defaults: 15g coffee, 6.0 grind, 250ml water, 91°C temp
2026-02-02 19:31:14 +00:00
Jon Seager
6b7b7f9c6e
feat(brews): add application routes and server integration
- Add brew routes: page, create, list, get, delete
- Add timeline event creation when brews are logged
- Add brew_repo to AppState
- Add Conflict variant to AppError for 409 responses
2026-02-02 19:30:45 +00:00
Jon Seager
5f8de35404
feat(brews): add infrastructure layer
- Add SqlBrewRepository with transactional insert that deducts from bag
- Return Conflict error when insufficient coffee in bag
- Add BrewsClient for CLI HTTP operations
2026-02-02 19:30:14 +00:00
Jon Seager
91bd3172ea
feat(brews): add domain layer and database migration
- Add BrewId typed wrapper
- Add Brew, BrewWithDetails, NewBrew, BrewFilter, BrewSortKey
- Add BrewRepository trait with transactional insert semantics
- Add brews table with foreign keys to bags and gear
- Update timeline_events constraint to include 'brew' entity type
2026-02-02 19:29:23 +00:00
Jon Seager
2bd008a8d7
chore(lint): enable clippy pedantic and restriction lints
- Add [lints.clippy] section to Cargo.toml with pedantic group
- Cherry-pick restriction lints: dbg_macro (deny), todo, unwrap_used, expect_used (warn)
- Allow noisy pedantic lints (missing_errors_doc, module_name_repetitions, etc.)
- Fix unnecessary Result wrappers in token and user repository to_domain functions
- Merge duplicate match arms in TimelineEventViewModel
- Add justified #[allow] attributes for startup code and tests
2026-02-02 17:55:16 +00:00
Jon Seager
00477bdbc0
feat(roast): add update command for consistency with other entities
- Add PUT /api/v1/roasts/:id route handler
- Add update() method to roasts HTTP client
- Add UpdateRoastCommand to CLI with optional fields
- Add CLI tests for roast update authentication and functionality

Brings roast entity in line with roaster, bag, and gear which all
support add/list/get/update/delete operations.
2026-02-02 17:09:32 +00:00
Jon Seager
0090c4ba43
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
2026-02-02 17:02:46 +00:00
Jon Seager
b3c13faacf
fix(api): make all roast and bag endpoints return enriched types
Previously, list endpoints returned enriched types (RoastWithRoaster,
BagWithRoast) with related entity names, while get/create/update
endpoints returned bare types without this information.

This change makes all endpoints consistent by returning enriched types:
- Added get_with_roaster and get_with_roast repository methods
- Created define_enriched_get_handler! macro for custom getter methods
- Updated create and update handlers to fetch enriched data after write
- Updated CLI client to expect enriched types
2026-02-02 16:43:31 +00:00
Jon Seager
bee94d2a2e
fix(api): flatten RoastWithRoaster JSON response for consistency
The list-roasts API was returning nested objects with a "roast" field,
which was inconsistent with other list APIs in the codebase. Added
serde's #[serde(flatten)] attribute to make the response flat while
maintaining the internal struct composition.
2026-02-02 16:32:56 +00:00
Jon Seager
6bbbafbef4
refactor(gear): remove notes field from Gear entity
The notes field was not providing enough value to justify its presence.
Simplified the Gear entity by removing notes from:
- Domain structs (Gear, NewGear, UpdateGear)
- SQL repository queries and GearRecord
- HTTP client methods
- CLI commands (--notes flag)
- Web views and templates
- All related tests

Added migration 0008_remove_gear_notes.sql to drop the column.
2026-02-02 16:16:54 +00:00
Jon Seager
708d89d452
fix: resolve clippy warnings for argument count and FromStr trait
- Allow too_many_arguments for AppState::new since 8 repos are needed
- Implement FromStr trait for GearCategory instead of custom from_str
  method to follow Rust conventions
- Update callers to use map_err for Result handling
2026-02-02 16:12:13 +00:00
Jon Seager
d1ebe835f9
fix: ensure gear events show up in timeline 2026-02-02 15:58:42 +00:00
Jon Seager
74a8bef475
feat(web): add Gear web UI with reactive updates
Implement complete web interface for the Gear entity:
- Add GearView model with category badges and formatted display
- Create GearTemplate and GearListTemplate for Askama rendering
- Build main gear page with collapsible add form (Datastar-powered)
- Implement gear list table with sortable columns and pagination
- Use trash icon for delete actions matching roasts table design
- Add Gear navigation link in main menu between Bags and Timeline
- Integrate gear events into timeline view with proper labels and links

The web UI follows the established patterns from other entities with
Datastar for reactive fragment updates and proper authentication gating.

Apply code formatting fixes across all gear-related modules.
2026-02-02 15:35:57 +00:00
Jon Seager
1f5561d7ab
feat(cli): add Gear CLI commands
Implement complete CLI interface for managing gear via brewlog CLI.

Commands (presentation/cli/gear.rs):
- add-gear: Create gear with --category, --make, --model, --notes flags
- list-gear: List all gear with optional --category filter
- get-gear: Retrieve single gear by --id
- update-gear: Update gear fields (make, model, notes)
- delete-gear: Delete gear by --id

Uses define_get_command! and define_delete_command! macros for get/delete
operations to reduce boilerplate.

Command Registration:
- Register gear module in presentation/cli/mod.rs
- Add commands to Commands enum: AddGear, ListGear, GetGear, UpdateGear, DeleteGear
- Wire command execution in main.rs

All commands output JSON and use BrewlogClient for HTTP API calls.

Examples:
  brewlog add-gear --category grinder --make Baratza --model Encore
  brewlog list-gear --category brewer
  brewlog update-gear --id 1 --notes "Updated notes"
2026-02-02 15:24:34 +00:00
Jon Seager
1ef447157e
feat(application): add Gear API routes and web handlers
Implement complete HTTP interface for Gear entity with API endpoints and web views.

Route Handlers (application/routes/gear.rs):
- gear_page(): Web page handler with Datastar fragment support
- create_gear(): Create gear with timeline event logging
- list_gear(): JSON API with optional category filter
- get_gear(): Single gear retrieval (using macro)
- update_gear(): Update gear fields
- delete_gear(): Delete gear (using macro)
- load_gear_page(): Helper for paginated gear data
- render_gear_list_fragment(): Fragment rendering for Datastar updates
- NewGearSubmission: Input validation with category parsing and empty checks

Timeline Integration:
- Logs "added" events with Category, Make, Model details
- Events created in route handler (route-based approach like Bags)

Route Registration (application/routes/mod.rs):
- API routes: GET/POST /api/v1/gear, GET/PUT/DELETE /api/v1/gear/:id
- Web route: GET /gear

AppState Updates (application/server.rs):
- Add gear_repo field to AppState struct
- Initialize SqlGearRepository in serve() function
- Update AppState::new() to include gear_repo parameter

Supports both JSON API and form submissions with Datastar reactive updates.
2026-02-02 15:21:26 +00:00
Jon Seager
2309a696cc
feat(infrastructure): implement Gear repository and HTTP client
Add SQL repository and HTTP client implementations for Gear entity.

SQL Repository (infrastructure/repositories/gear.rs):
- SqlGearRepository with CRUD operations
- order_clause() for sorting: Make/Model (case-insensitive), Category, CreatedAt
- build_where_clause() for category filtering
- to_domain() converts GearRecord to domain Gear with category parsing
- Uses push_update_field! macro for partial updates
- Proper error handling with RepositoryError types

HTTP Client (infrastructure/client/gear.rs):
- GearClient for CLI access to API endpoints
- Methods: create(), list(), get(), update(), delete()
- Supports optional category filter in list()
- Error context with anyhow for user-friendly messages

Module Registration:
- Register gear module in infrastructure/repositories/mod.rs
- Register gear module and gear() method in infrastructure/client/mod.rs

Follows the exact patterns from BagRepository and BagsClient.
2026-02-02 15:19:19 +00:00
Jon Seager
b794d8a63f
feat(domain): add Gear entity with database migrations
Add Gear entity to track brewing equipment (grinders and brewers) with complete domain layer implementation.

Database changes:
- migrations/0006_add_gear.sql: Create gear table with category CHECK constraint and indexes
- migrations/0007_update_timeline_for_gear.sql: Document 'gear' as valid timeline entity type

Domain layer:
- Add GearId typed ID wrapper
- Create domain/gear.rs with:
  - GearCategory enum (Grinder/Brewer) with string conversion methods
  - Gear entity with make, model, notes fields
  - NewGear and UpdateGear DTOs
  - GearFilter for category-based filtering
  - GearSortKey with Make (default), Model, Category, CreatedAt options
- Add GearRepository trait to domain/repositories.rs with standard CRUD operations
- Register gear module in domain/mod.rs

This follows the same architectural pattern as the Bag entity.
2026-02-02 15:14:23 +00:00
Jon Seager
4bae7f1762
refactor(routes): re-export is_authenticated for shorter imports
Add pub(crate) re-export of is_authenticated from routes/mod.rs,
allowing handlers to use super::is_authenticated instead of the
verbose crate::application::routes::auth::is_authenticated path.

Updated 9 call sites across roasters.rs, roasts.rs, bags.rs, and
timeline.rs.
2026-02-02 14:32:30 +00:00
Jon Seager
7b0b6409e5
refactor(repos): use SQLx is_unique_violation for constraint checks
Replace string matching on "UNIQUE constraint failed" with SQLx's
type-safe is_unique_violation() method in roasters.rs and roasts.rs.

This aligns with users.rs and tokens.rs, and ensures the check works
across different database backends (SQLite, PostgreSQL) without
depending on error message formatting.
2026-02-02 14:30:30 +00:00
Jon Seager
2e4c8722ca
refactor(bags): align order_clause with other repositories
- Use method access (sort_key(), sort_direction()) instead of field
  access for consistency with roasters.rs and roasts.rs
- Add secondary sort clauses (e.g., created_at DESC) as tiebreakers
  to ensure deterministic pagination results
2026-02-02 14:28:45 +00:00
Jon Seager
c941cd83fb
docs(bags): add SAFETY comment for SQL string interpolation
Document why direct format!() interpolation is acceptable in
build_where_clause(): the values are type-safe (bool outputs literal
TRUE/FALSE, roast_id is i64 from typed wrapper). Warns future
developers to use parameterized queries if string fields are added.
2026-02-02 14:27:03 +00:00
Jon Seager
3a9fb16793
refactor(bags): replace multiple list methods with composable filter
- Add BagFilter struct with constructor methods (all, open, closed, for_roast)
- Replace 5 repository methods with single list(filter, request) method
- Add build_where_clause helper for dynamic WHERE clause construction
- Update all callers in bags and roasts routes

This eliminates method explosion when adding new filters - now only
BagFilter and build_where_clause need updating instead of adding
new repository methods.
2026-02-02 14:15:18 +00:00
Jon Seager
2d183e6955
refactor(routes): add macros to reduce route handler boilerplate
- Create define_get_handler! macro for GET-by-ID endpoints
- Create define_delete_handler! macro for DELETE endpoints with Datastar support
- Apply macros to roasters, roasts, and bags route modules
- Reduces 6 handlers from ~78 lines to ~12 lines total
2026-02-02 13:40:14 +00:00
Jon Seager
d689e48328
refactor(infra): standardize SQL query construction across repositories
- Add push_update_field! macro to reduce UPDATE query boilerplate
- Rename sort_clause() to order_clause() for consistency
- Convert bags.rs update method from string concatenation to QueryBuilder
- Apply macro to roasters.rs, roasts.rs, and bags.rs update methods

This reduces ~100 lines of repetitive code and ensures consistent
patterns for building dynamic UPDATE queries across all repositories.
2026-02-02 13:09:33 +00:00
Jon Seager
3e0aa1653f
chore(refactor): define macro for get/delete cli commands 2026-02-02 13:00:48 +00:00
Jon Seager
6611643424
chore: move RepositoryError into dedicated file 2026-02-02 12:57:15 +00:00
Jon Seager
ab9d4bfa13
feat: add amount to Bag Finished events 2025-11-27 15:10:17 +00:00
Jon Seager
f750107bb1
fix: add an action field to timeline events 2025-11-27 14:52:13 +00:00
Jon Seager
144257c2c8
fix: ensure bags are closed properly and closures are on the timeline 2025-11-27 14:42:38 +00:00
Jon Seager
673f76f09a
fix: ensure the list-bags command with no id lists all bags 2025-11-27 14:32:48 +00:00
Jon Seager
57260a71c2
fix: remove superfluous /bags/:id/finish endpoint 2025-11-27 14:24:26 +00:00
Jon Seager
7eab263071
chore: slight simplifications to bags 2025-11-27 14:03:00 +00:00
Jon Seager
d708a0e112
feat: add bags web views and templates 2025-11-27 14:02:55 +00:00
Jon Seager
f0cef61513
feat: add bags CLI commands 2025-11-27 14:02:38 +00:00
Jon Seager
ef6f30d6c3
feat: add bags API client 2025-11-27 14:01:40 +00:00