Commit graph

98 commits

Author SHA1 Message Date
Jon Seager
6e2c59814c
feat(cups): add web views, templates, and navigation
- Add CupView, CafeOptionView, RoastOptionView view models
- Add CupsTemplate (full page) and CupListTemplate (fragment)
- Create cups.html with form and cup_list.html with responsive table
- Add Cups link to desktop and mobile navigation
- Add cup timeline event link and label support
2026-02-03 16:36:35 +00:00
Jon Seager
4e644d5528
feat(cups): add CLI commands for cup management
- Add cup add/list/get/update/delete subcommands
- Wire Cup variant into Commands enum and main dispatch
2026-02-03 16:36:27 +00:00
Jon Seager
a6fad343fc
feat(cups): add route handlers and wire into application state
- Add cup_repo to AppState and server startup
- Register API routes: GET/POST /cups, GET/PUT/DELETE /cups/:id
- Register web route: GET /cups with Datastar fragment support
- Rating validation (1-5) on create and update
2026-02-03 16:36:23 +00:00
Jon Seager
c2e9c7395c
feat(cups): add HTTP client for cups API
- Add CupsClient with create, list, get, update, delete methods
- Register cups() accessor on BrewlogClient
2026-02-03 16:36:14 +00:00
Jon Seager
928c4c9aea
feat(cups): add SQL repository with timeline event integration
- Implement CupRepository trait for SqlCupRepository
- Transactional insert creates cup and timeline event atomically
- Support filtering by cafe_id/roast_id, search across roast/roaster/cafe names
- Use QueryBuilder with push_update_field! macro for updates
2026-02-03 16:36:10 +00:00
Jon Seager
ac7a4f9cf9
feat(cups): add migration and domain layer for cup entity
- Create cups table with roast_id/cafe_id FKs, optional notes and rating
- Update timeline_events CHECK constraint to include 'cup' entity type
- Add CupId typed wrapper, Cup/CupWithDetails/NewCup/UpdateCup structs
- Add CupFilter, CupSortKey, and CupRepository trait
2026-02-03 16:35:56 +00:00
Jon Seager
5e2ee63a1e
test(cafes): add integration tests for nearby cafe search API
- Add wiremock dev-dependency for HTTP mocking
- Make Nominatim URL configurable via AppState for test injection
- Add spawn_app_with_nominatim_mock() test helper
- Add 6 integration tests covering success, empty results,
  authentication, validation, and upstream failure scenarios
2026-02-03 16:03:54 +00:00
Jon Seager
4cd1203864
feat(cafes): add nearby cafe search via Nominatim API
- Add OSM infrastructure module with Nominatim forward search, viewbox
  location bias, and haversine distance calculation
- Add reqwest::Client to AppState for server-side HTTP requests
- Add GET /api/v1/nearby-cafes endpoint (authenticated) accepting
  lat, lng, and text query parameters
- Check website, contact:website, url, contact:url, and brand:website
  tags to maximise website coverage from OSM data
2026-02-03 15:56:28 +00:00
Jon Seager
3ab1c50cc8
refactor(timeline): remove map pin icon from timeline title
Position is already shown as a clickable detail row in the card body,
making the title icon redundant. Also removes the unused map_link field
from TimelineEventView.
2026-02-03 15:10:21 +00:00
Jon Seager
3f83d74e20
feat(cafes): add CLI commands, web views, and templates
Add CLI subcommands (add, list, get, update, delete) with negative
number support for coordinates. Add CafeView with map URL generation,
Askama templates for list/detail pages, and nav link between Brews
and Gear.
2026-02-03 14:53:00 +00:00
Jon Seager
1781bff48d
feat(cafes): add route handlers and wire into application state
Add cafe route handlers (CRUD API + web pages with Datastar fragment
support). Register cafe_repo in AppState and mount API and web routes.
2026-02-03 14:52:51 +00:00
Jon Seager
677b77d70d
feat(cafes): add SQL repository, HTTP client, and backup support
Implement SqlCafeRepository with CRUD operations and timeline event
creation. Add CafesClient for CLI HTTP access. Wire cafes into backup
export/restore and empty-database verification.
2026-02-03 14:52:44 +00:00
Jon Seager
c6ad6a02c7
feat(cafes): add migration and domain layer for cafe entity
Add cafes table with name, slug, city, country, latitude, longitude,
website, and notes fields. Define domain types (Cafe, NewCafe, UpdateCafe),
CafeId typed wrapper, CafeRepository trait, and CafeSortKey enum.
2026-02-03 14:52:37 +00:00
Jon Seager
f1e8984ffa
refactor(roasts): extract helpers to remove too_many_lines suppression
- Add empty_to_none() to replace four repeated empty-string-to-None blocks
- Extract insert_timeline_event() from insert() to isolate timeline logic
- Remove #[allow(clippy::too_many_lines)] from RoastRepository impl
2026-02-03 14:09:53 +00:00
Jon Seager
09a629db9a
feat(brews): pre-populate Add Brew form with last recorded brew values
- Add BrewDefaultsView to carry last brew's values to the template
- Extract form data loading into load_brew_form_data() helper
- Use defaults for signal initialization and dropdown pre-selection
- Retain submitted values after form submission instead of resetting
2026-02-03 14:01:41 +00:00
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