- 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
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.
- 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
- 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
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
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.
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.
- 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
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.
- 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.
Code Quality Improvements:
- Fix hex literal grouping in ID generator (0xB10C_1D -> 0x00B1_0C1D)
- Rename ListQuery::default() to default_query() to avoid confusion with Default trait
- Use div_ceil() instead of manual ceiling division
- Remove unnecessary borrows in auth token generation and hashing
- Simplify nested if statements in error handling
Documentation:
- Add comprehensive authentication section to README
- Document environment variables for server and CLI
- Add security best practices and considerations
- Document password hashing (Argon2id), token storage (SHA-256), and session management
- Include step-by-step authentication setup guide
- Add production deployment recommendations
All 70 tests pass (8 unit + 46 server + 16 CLI) ✅
Co-authored-by: jnsgruk <668505+jnsgruk@users.noreply.github.com>
- Add sessions table to store session tokens with expiration
- Create Session domain model and SessionRepository trait
- Implement SqlSessionRepository for session persistence
- Update is_authenticated() to validate tokens against database
- Sessions expire after 30 days
- Session tokens hashed with SHA-256 before storage
- Delete sessions from database on logout
- Update all page handlers to properly validate sessions
This prevents session hijacking by ensuring only valid, unexpired
tokens stored in the database can authenticate requests.
Co-authored-by: jnsgruk <668505+jnsgruk@users.noreply.github.com>