Commit graph

65 commits

Author SHA1 Message Date
Jon Seager
0030f87eed
refactor(timeline): extract event creation into service layer
Introduce application services that encapsulate entity creation +
timeline event recording. Route handlers call service.create() instead
of repo.insert() + inline timeline construction.

- Add define_simple_service! macro for roaster/cafe/gear services
- Add custom services for roasts, bags, brews, cups (need enrichment
  or cross-entity lookups)
- Add to_timeline_event() methods to all 7 domain entity types
- Remove timeline SQL from 4 repository insert() methods
- Delete brew_timeline_event() helper from brews route handler
- Update backup test to use services (timeline events created naturally)
- Document service layer pattern in CLAUDE.md
2026-02-06 10:42:52 +00:00
Jon Seager
010b700595
feat(brews): add quick notes
Predefined toggleable tags for brews: Good, Too Fast, Too Slow,
Too Hot, Under Extracted, Over Extracted.

- Add quick_notes TEXT column storing JSON array
- Add QuickNote enum with label/form-value/positive helpers
- Update repository to encode/decode JSON, backup to export notes
- Add toggle pill UI on brew form with Datastar signals
- Show pills on home page cards (muted for Good, amber for others)
- Show comma-joined text in data table Notes column
- Add --quick-notes flag to CLI, update HTTP client
- Update bootstrap script with sample quick notes
2026-02-05 20:42:03 +00:00
Jon Seager
35ea246ba2
test: add coverage for homepage, check-in, scan, and AI extraction
- 12 page smoke tests: homepage (empty/with data/stats), login,
  auth-gated redirects (check-in, add, account), authenticated page
  loads, logout, and scan redirect
- 5 check-in API tests: existing cafe, new cafe creation, auth,
  and validation
- 7 scan API tests: roaster+roast creation, slug-based reuse, bag
  creation with timeline event, auth, and field validation
- 9 AI extraction tests: all 3 extract endpoints with wiremock-mocked
  OpenRouter, both JSON and Datastar signal responses, auth and
  empty input validation
2026-02-05 19:19:05 +00:00
Jon Seager
a9d97d40bd
refactor(ai): make OpenRouter URL configurable for testability
Add url parameter to ai::extract_roaster(), extract_roast(), and
extract_bag_scan() instead of using a hardcoded constant. Wire through
AppState.openrouter_url (defaulting to the production URL), matching
the existing pattern used for foursquare_url.

Add test infrastructure: session creation helper, page_url() on
TestApp, and spawn_app_with_openrouter_mock() for wiremock-based
AI extraction testing.
2026-02-05 19:18:51 +00:00
Jon Seager
6d12b71482
refactor(cups): remove rating field
Drop the rating column from cups. This removes the field from the
domain model, repository, API routes (including the PUT/update
endpoint), CLI, and all related tests.
2026-02-05 17:02:42 +00:00
Jon Seager
0a93df845f
feat(ai): record usage after each OpenRouter extraction
Wire AiUsageRepository into AppState and add a fire-and-forget
record_ai_usage helper. All extraction route handlers now capture
the usage tuple and record it in the background.
2026-02-05 12:09:36 +00:00
Jon Seager
4bc64dd12f
test(auth): update tests for passkey migration
- Replace password-based token creation with direct DB inserts in CLI helpers
- Use INSERT OR IGNORE for concurrent test thread safety on shared user
- Update server test helpers with passkey/registration_token repos and WebAuthn state
- Remove password-based auth tests, keep bearer token validation tests
- Set BREWLOG_RP_ID, RP_ORIGIN, OPENROUTER_API_KEY, FOURSQUARE_API_KEY in test env
- Use localhost instead of 127.0.0.1 for WebAuthn RP ID compatibility
2026-02-05 11:00:20 +00:00
Jon Seager
4bdfc661f9
refactor: consolidate entity pages into unified /data and /add views
Replace per-entity pages (/roasters, /roasts, /bags, /brews, /gear,
/cafes, /cups) and detail pages with a single tabbed /data view and
a dedicated /add page for entity creation.

- Add /data route with tab-based navigation using Datastar
- Add /add route consolidating all create forms
- Remove per-entity page handlers and standalone templates
- Remove detail page routes, handlers, and templates
- Update ListNavigator to accept String paths for query-param URLs
- Update home page and timeline links to use new /data?type=X paths
- Update nav to reference /data instead of individual entity pages
2026-02-04 20:51:30 +00:00
Jon Seager
50a372015c
feat(backup): expose backup/restore via authenticated API endpoints
- Add GET /api/v1/backup and POST /api/v1/backup/restore endpoints
  behind AuthenticatedUser
- Add BackupService to AppState and BackupClient for HTTP access
- Update CLI backup/restore to use API instead of direct DB access
- Remove --database-url flag from backup and restore commands
- Increase body limit to 50MB for restore endpoint
- Add API and CLI tests for auth, export, restore, and round-trip
- Update README to document auth requirement and API endpoints
2026-02-04 19:38:49 +00:00
Jon Seager
305bc5f5a5
fix(tests): abort server tasks on test cleanup to prevent zombie processes
Store the tokio AbortHandle in TestApp and implement Drop to abort the
spawned server task. Previously the JoinHandle was silently dropped,
leaving server tasks running indefinitely after tests completed.
2026-02-04 16:37:08 +00:00
Jon Seager
a31c91211c
refactor: require OpenRouter and Foursquare API keys at startup
- Remove has_ai_extract/has_foursquare conditionals from all templates
- Remove boolean fields from template structs and route handlers
- Remove has_ai_extract()/has_foursquare() methods from AppState
- Change API key fields from Option<String> to String in ServerConfig
  and AppState
- Validate keys in run_server() with clear error messages
- Remove runtime key checks from extraction/nearby route handlers
- Update README and CLAUDE.md to reflect required configuration
2026-02-04 16:13:04 +00:00
Jon Seager
4f7d31d47a
refactor(roasters): remove notes field from Roaster entity
- Add migration 0016 to drop notes column from roasters table
- Remove notes from domain structs, repositories, views, CLI, routes
- Remove notes from AI extraction prompts and ExtractedRoaster struct
- Remove notes from scan page form and submission handler
- Update templates, bootstrap script, backup module, and tests
2026-02-03 21:18:48 +00:00
Jon Seager
ae3c1090de
refactor(cafes,cups): remove notes field from Cafe and Cup entities
- Add migrations 0014 and 0015 to drop notes columns
- Remove notes from domain structs, repositories, views, CLI, routes
- Remove notes textarea/columns from web templates
- Update bootstrap script, tests, backup module, and README
2026-02-03 21:09:34 +00:00
Jon Seager
fb31d0a0c4
refactor(cafes): remove notes field from Cafe entity
- Add migration 0014 to drop the notes column from cafes table
- Remove notes from domain structs, repository, view model, CLI, routes
- Remove notes textarea/column from web templates
- Update bootstrap script and all test files
2026-02-03 21:02:37 +00:00
Jon Seager
5f3507fa92
feat(nearby): add city-based search via Foursquare near param
- Add SearchLocation enum to support coordinates or named location
- Accept optional `near` query param as alternative to lat/lng
- Add city text input with checkbox toggle in cafes template
- Foursquare `near` param enables searching any city worldwide
2026-02-03 20:36:59 +00:00
Jon Seager
ecb010e812
refactor(nearby): replace Nominatim with Foursquare Places API
- Replace osm.rs with foursquare.rs using Foursquare Places Search API
- Add isocountry crate for ISO 3166-1 country code to name conversion
- Override verbose country names (e.g. "United Kingdom" instead of
  "United Kingdom of Great Britain and Northern Ireland")
- Add BREWLOG_FOURSQUARE_API_KEY env var for API authentication
- Update route handler, tests, and template to use Foursquare
- Modernise cafes template JS to ES6+ (const/let, arrow fns, template
  literals)
2026-02-03 20:36:11 +00:00
Jon Seager
b668d368e7
feat(ai): add AI-powered extraction for roaster and roast forms
Users can upload a photo of a coffee bag or type a text prompt to
auto-fill roaster/roast form fields via OpenRouter's chat completions
API. The feature is opt-in: hidden when BREWLOG_OPENROUTER_API_KEY is
not set.

- Add src/infrastructure/ai.rs with OpenRouter client, prompt
  templates, JSON extraction from model responses, and unit tests
- Add extract-roaster and extract-roast API endpoints behind auth
- Add extraction UI (photo button + text input) to both form templates
- Add openrouter_api_key and openrouter_model to CLI args and AppState
- Enable reqwest gzip feature for compressed API responses
- Default model set to openrouter/free (auto-selects available models)
- Prompts instruct model to use web search for missing details
2026-02-03 18:00:19 +00:00
Jon Seager
114f68177a
test(cups): add integration tests for cups API
- Test CRUD operations: create, list, get, update, delete
- Test auth requirement, optional fields, enriched data
- Test validation: no changes returns 400, nonexistent returns 404
- Update test helpers with cup_repo wiring
2026-02-03 16:36:40 +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
069e861255
test(cafes): add API, CLI, Datastar, timeline, and backup tests
Add 14 server API tests, 6 CLI tests, 4 Datastar fragment tests,
1 timeline event test, and backup round-trip verification. Wire
cafe_repo into test helpers.
2026-02-03 14:53:08 +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
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
cb1cdc5c5b
test(brews): add filter paper coverage to brew API tests
- Add test for creating a brew with filter paper
- Add test verifying filter_paper_name is None when omitted
- Update enriched data test to assert on filter_paper_name
- Add filter_paper category to test helper
2026-02-03 12:50:33 +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
b3d697978c
test(brews): add server and CLI integration tests
- Test create, list, get, delete operations
- Test bag deduction and conflict on insufficient coffee
- Test CLI commands with defaults
- Add create_default_gear helper
2026-02-02 19:32:35 +00:00
Jon Seager
c7ea7420ae
fix(tests): use CARGO_BIN_EXE for binary path in CLI tests
The CLI tests hardcoded target/debug/brewlog, but nix builds in
release mode. Use Cargo's CARGO_BIN_EXE_brewlog env var which
automatically points to the correct binary location.

This enables running tests during nix build (removes doCheck = false).
2026-02-02 17:37:04 +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
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
c1a5d763e2
test(gear): add comprehensive test suite for gear entity
Implement complete test coverage for the Gear entity across CLI, API,
timeline, and Datastar integration layers. This completes the Gear
entity implementation with 32 dedicated tests ensuring all CRUD
operations, authentication requirements, and reactive UI updates work
correctly.

Test Coverage:
- CLI commands (11 tests): Authentication, CRUD operations, category
  filtering, notes handling
- API endpoints (13 tests): HTTP status codes, validation, auth
  requirements, filtering
- Timeline integration (1 test): Gear creation events appear correctly
- Datastar fragments (7 tests): Fragment rendering vs full page/JSON
  responses for list, create, update, delete

Changes:
- Add tests/cli/gear_cli.rs with CLI integration tests
- Add tests/server/gear_api.rs with API integration tests
- Update tests/server/timeline.rs with gear timeline test
- Update tests/server/datastar.rs with gear fragment rendering tests
- Register test modules in tests/cli/main.rs and tests/server/main.rs
- Clean up unused imports in tests/server/helpers.rs

All 140 tests pass (8 unit + 37 CLI + 95 server integration).
2026-02-02 16:09:02 +00:00
Jon Seager
df0ebc283b
test(server): add integration tests for datastar endpoints 2026-02-02 14:02:55 +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
02c6f6d675
test: add test for listing all bags 2025-11-27 14:32:56 +00:00
Jon Seager
ec8dbea7ea
test: add server tests for bags 2025-11-27 14:02:59 +00:00
Jon Seager
5252cacad0
test: add cli tests for bags 2025-11-27 14:02:58 +00:00
Jon Seager
02824a90f1
feat: add username/password flags to create-token command 2025-11-26 16:32:40 +00:00
Jon Seager
65e1e141ea
fix(test): fix race condition in CLI test 2025-11-26 16:28:13 +00:00
Jon Seager
4358fa63dc
feat: add BREWLOG_ADMIN_USERNAME to select admin username on first start 2025-11-26 16:27:50 +00:00
Jon Seager
e79db7c1d3
chore: rename server crate to application 2025-11-25 21:40:39 +00:00
Jon Seager
f88a880d07
feat!: update to a more human friendly url structure for roasters/roasts 2025-11-25 21:34:08 +00:00
Jon Seager
42d0f71eb1
feat!: use numeric, database-generated IDs throughout 2025-11-25 18:21:04 +00:00
copilot-swe-agent[bot]
8d25353f04
refactor(test): remove redundant cargo build from CLI test setup
The project is already built by cargo test before running tests,
so the explicit cargo build step in the TEST_SERVER lazy static
was redundant and wasteful.

All 70 tests pass (8 unit + 46 server + 16 CLI) 

Co-authored-by: jnsgruk <668505+jnsgruk@users.noreply.github.com>
2025-11-25 16:28:50 +00:00
copilot-swe-agent[bot]
c91dd5d78d
test(auth): add CLI test for revoked tokens and server tests for session auth
- Add test_revoked_token_cannot_be_used to CLI tests
- Add test_session_authentication_via_login to verify session cookies work
- Add test_invalid_session_cookie_fails to verify unauthenticated requests fail
- Add test_logout_invalidates_session to verify logout clears sessions
- Add test_fake_session_cookie_fails to verify forged cookies don't work
- Enable cookies feature for reqwest in dev-dependencies

All 70 tests pass (8 unit + 46 server + 16 CLI) 

Co-authored-by: jnsgruk <668505+jnsgruk@users.noreply.github.com>
2025-11-25 16:28:49 +00:00
copilot-swe-agent[bot]
4c040f2c58
fix(security): validate session tokens against database
- 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>
2025-11-25 16:28:48 +00:00
copilot-swe-agent[bot]
650ca7a372
docs(test): clarify purpose of underscore-prefixed fields in SharedServer
Co-authored-by: jnsgruk <668505+jnsgruk@users.noreply.github.com>
2025-11-25 16:28:47 +00:00
copilot-swe-agent[bot]
d290ac8528
refactor(test): clean up CLI test helpers and improve code clarity
Co-authored-by: jnsgruk <668505+jnsgruk@users.noreply.github.com>
2025-11-25 16:28:46 +00:00
copilot-swe-agent[bot]
f98ac3f87d
fix(test): make CLI tests fully functional with working server and proper test isolation
- Fix server command arguments (--bind-address instead of --port, --database-url instead of --database)
- Use BREWLOG_URL environment variable for CLI commands (not BREWLOG_SERVER)
- Implement shared test server with proper mutex handling to avoid poisoning
- Create tokens via API (not interactive CLI) to avoid stdin issues
- Fix roasts tests to include required --tasting-notes argument
- Fix roasts list test to handle RoastWithRoaster nested JSON structure
- Remove create-token CLI tests (interactive stdin handling too complex for automation)
- Configure CLI tests to run serially with --test-threads=1 to share single server

All tests pass:
-  8 unit tests (password/token generation)
-  42 server API tests (including 9 auth tests)
-  15 CLI tests (roasters: 6, roasts: 5, tokens: 4)
-  Total: 65 tests passing

Co-authored-by: jnsgruk <668505+jnsgruk@users.noreply.github.com>
2025-11-25 16:28:45 +00:00