Commit graph

282 commits

Author SHA1 Message Date
Jon Seager
edffb1679d
chore: update dependencies (#15)
* build(deps): bump actions/cache from 4 to 5

Bump the actions-deps group.

* build(deps): bump cargo-deps group (anyhow, chrono, clap, uuid, tempfile)

- anyhow 1.0.101 → 1.0.102
- chrono 0.4.43 → 0.4.44
- clap 4.5.57 → 4.5.60
- uuid 1.20.0 → 1.21.0
- tempfile 3.24.0 → 3.25.0

* build(deps): bump sqlx from 0.7.4 to 0.8.6

* build(deps): bump rand from 0.8.5 to 0.9.2

Migrate API changes:
- Replace OsRng.fill_bytes() with rand::rng().fill_bytes()
- Replace rand::thread_rng() with rand::rng()

* build(deps): bump thiserror from 1.0.69 to 2.0.18
2026-03-05 11:03:55 +00:00
Jon Seager
937ee4c5f2
feat: store bag finished_at as full datetime for correct timeline ordering
The finished_at field previously stored only a date, causing bag "finished"
timeline events to sort before same-day brews. Now stores a full datetime
and uses the actual close time for timeline ordering.
2026-02-24 21:38:39 +00:00
Jon Seager
6a2bd5e563
fix: allow multiple origins for mixed roasts 2026-02-24 11:03:20 +00:00
Jon Seager
dfb7e86f6d
fix: use Axum DefaultBodyLimit so per-route overrides work
Replace tower-http RequestBodyLimitLayer with Axum's DefaultBodyLimit
for the global 5 MB request body cap. RequestBodyLimitLayer enforced a
hard limit that prevented per-route DefaultBodyLimit overrides (backup
restore at 50 MB, scan/image at 10 MB) from taking effect, causing
HTTP 413 on backup restore.
2026-02-22 10:03:43 +00:00
Jon Seager
76da3d9bff
fix: require discoverable credentials for passkey registration
The webauthn-rs `start_passkey_registration` sets residentKey to
"discouraged", which iOS Safari respects strictly — creating
non-discoverable credentials that never appear in autofill. Desktop
password managers ignore this flag. Patch the creation challenge to
require resident keys so discoverable passkeys work on all platforms.
2026-02-15 12:29:07 +00:00
Jon Seager
5a0321b226
feat: add WebAuthn Conditional UI for passkey autofill
Enables password managers (1Password, iCloud Keychain) to offer passkey
suggestions via autofill on the login page, matching behavior of other
passkey-enabled websites.
2026-02-15 11:38:53 +00:00
Jon Seager
0e3847ade9
feat: sync timeline events with entity edits
Add background timeline rebuild task (mirroring stats cache pattern)
that refreshes denormalized timeline event snapshots when entities are
updated. Includes cascade logic so editing a roaster refreshes timeline
events for its roasts, bags, brews, and cups.

- Add update_by_entity/delete_by_entity/delete_all to TimelineEventRepository
- Add TimelineInvalidator with debounced background rebuild task
- Add invalidate() calls to all 7 entity update handlers
- Add delete_by_entity cleanup to define_delete_handler! macro
- Add gear_id filter to BrewFilter for cascade traversal
- Add `brewlog timeline rebuild` CLI command for full rebuild
- Add 5 integration tests for timeline sync behavior
2026-02-15 11:11:07 +00:00
Jon Seager
12dcc37cbe
fix: persist session cookie with max-age for Safari PWA support
Session cookie lacked a Max-Age attribute, making it ephemeral. Safari's
standalone PWA mode aggressively clears session cookies on app suspend,
causing frequent logouts. Set Max-Age to 30 days to match the server-side
session lifetime.
2026-02-15 10:36:05 +00:00
Jon Seager
5820c162f6
fix: address static assets code review findings
- Remove dead CSS color property in .tab-mobile
- Fix design token violations (text-red-500, fallback colors)
- Add disconnectedCallback to chip-scroll, image-upload, searchable-select
- Replace innerHTML with safe DOM APIs in world-map screen reader table
- Add credentials: same-origin to WebAuthn fetch calls
- Move page-specific scripts (donut-chart, location) out of base.html
- Add client-side image resizing (1920px max dimension)
- Resize app-icon-512.png from 2048x2048 to 512x512
- Document Datastar unsafe-eval CSP requirement
- Add static asset serving tests (16 routes)
- Add e2e tests for world-map, donut-chart, chip-scroll presence
- Add cache-busting query params to all static asset URLs
2026-02-13 17:13:02 +00:00
Jon Seager
920931ba17
refactor: fix template review findings (security, macros, tokens)
Address findings from the templates code review:

- Fix XSS in admin.html onclick handlers via data attributes
- Fix XSS in 5 edit page signal initializations via JSON serialization
- Fix register.html token exposure by moving to data attribute
- Add entity_icon, quick_notes_toggles, add_form_submit macros
- Replace hardcoded colors with design tokens (warning, error, success)
- Add warning design tokens to CSS theme
- Scope MutationObserver to main element
- Add defer to webauthn.js script tags
- Refactor login/register JS to arrow functions
- Guard lightbox script behind image_url check
- Fix else-if to elif in 5 templates
2026-02-13 16:20:30 +00:00
Jon Seager
ab4bcbaa0a
test: add unit tests for all reviewed presentation modules
Cover parse_created_at in CLI, StatsView::is_empty, format_datetime,
Paginated helpers, encode_uri_component, page_size_from_text,
build_map_data, build_coffee_info, used_percent edge cases,
brew_again_url construction, and parse_and_categorize splitting.
2026-02-13 16:20:29 +00:00
Jon Seager
472e851b80
refactor: standardize view model conversions on From trait
Convert single-argument from_domain() methods to impl From<T> for
consistency with the existing CafeView, RoasterView, and option view
patterns. Affected: BagView, BrewView, CupView, GearView,
GearDetailView, CafeDetailView, RoasterDetailView, TimelineEventView.

RoastView retains from_domain() since it takes extra parameters.
2026-02-13 16:20:27 +00:00
Jon Seager
31cb7c6c71
refactor: simplify NearbyCafeView iterator chain and remove unused _addr parameter
- Replace .iter().copied().cloned() with .into_iter() on str slices in
  NearbyCafeView::from
- Remove unused _addr: SocketAddr parameter from run_callback_server
  and its call site in tokens.rs
2026-02-13 16:20:23 +00:00
Jon Seager
766ac1432d
refactor: extract duplicated helpers for tasting notes, used_percent, format_datetime, and map builder
- Add parse_and_categorize() in tasting_notes.rs, replacing identical
  split-trim-categorize pipelines in mod.rs, roasts.rs, and timeline.rs
- Add used_percent() in bags.rs, deduplicating the calculation in
  BagView::from_domain and BagDetailView::from_parts
- Add format_datetime() in views/mod.rs, replacing 16 paired occurrences
  of .format("%Y-%m-%d") / .format("%H:%M") across 7 view files
- Add build_origin_roaster_map() in views/mod.rs, replacing identical
  map-entry building blocks in bags.rs, brews.rs, and roasts.rs detail views
2026-02-13 16:20:21 +00:00
Jon Seager
3d49236c13
refactor: move NearbyCafe to domain layer to fix dependency violation
The presentation layer was importing NearbyCafe directly from
infrastructure::foursquare, violating the dependency flow
(presentation -> application -> domain <- infrastructure). Introduce
NearbyCafeResult in domain::nearby_cafes and update all
references.
2026-02-13 16:20:20 +00:00
Jon Seager
f7b210f234
test: add unit tests for all reviewed domain modules
Add 57 unit tests across auth (sessions, registration tokens, API
tokens, username validation), coffee (roasters, cafes, brews, gear),
listing (pagination, sort keys, page calculations), analytics
(country stats), images (debug redaction), and entity type (roundtrip
serialization).
2026-02-13 16:20:18 +00:00
Jon Seager
bd4cb023fb
refactor: replace hand-written SortKey impls with define_sort_key! macro
Add define_sort_key! macro to listing.rs and convert all 8 SortKey
enum + impl blocks to use it, eliminating ~270 lines of repetitive
boilerplate across roasters, roasts, bags, brews, cups, cafes, gear,
and timeline.
2026-02-13 16:20:16 +00:00
Jon Seager
de49f7c3b1
refactor: add EntityType enum and typed IDs in TimelineBrewData
Replace stringly-typed entity references with a compile-time-safe
EntityType enum throughout timeline events, images, and repository
operations. Also replace raw i64 fields in TimelineBrewData with
typed BagId/GearId wrappers.
2026-02-13 16:20:14 +00:00
Jon Seager
c58c60c783
fix: security hardening and code health improvements in domain layer
- Remove danger-allow-state-serialisation feature from webauthn-rs
- Add #[serde(skip_serializing)] to Session and RegistrationToken hash fields
- Add custom Debug impls to redact hashes in Session and NewToken
- Add MAX_SESSION_DURATION (30d) and MAX_TOKEN_DURATION (7d) with clamping
- Add domain-level username validation (length + character constraints)
- Extract shared normalize_optional_field to coffee/mod.rs (DRY)
- Implement FromStr for QuickNote, delegate from_str_value to it
- Refactor UpdateRoaster/UpdateCafe normalize() to use shared helper
2026-02-13 16:20:11 +00:00
Jon Seager
7330e5b59e
refactor: convert to_domain methods to From/TryFrom trait impls
Replace ad-hoc to_domain/into_domain conversion methods on SQL
repository structs with idiomatic From and TryFrom trait implementations
on the record types, following standard Rust conventions.
2026-02-13 13:35:08 +00:00
Jon Seager
09bd7e61e4
test: add unit tests for support module helpers
Cover kebab_to_camel, render_signals_json, page_size_from_text,
parse_direction, and render_redirect_script with unit tests.
2026-02-13 13:06:19 +00:00
Jon Seager
19e191e922
perf: parallelize independent async calls in detail pages
Use tokio::try_join! to run independent repo queries and image URL
lookups concurrently in bag, brew, and checkin page handlers, and for
gear option loading in the brew form data loader.
2026-02-13 13:06:10 +00:00
Jon Seager
b8a26bbeca
feat: add entity counts to stats cache for home page
Add EntityCounts struct and StatsRepository::entity_counts() to query
per-entity row counts. Include them in CachedStats so the home page can
read counts from cache instead of issuing six list queries with LIMIT 1.
Derive Default on StatsView for the fallback case.
2026-02-13 13:06:04 +00:00
Jon Seager
9d25873243
refactor: reuse ListQuery for timeline page
Replace the hand-rolled TimelineQuery struct with the shared ListQuery
type and its into_request_and_search_with_default method.
2026-02-13 13:05:56 +00:00
Jon Seager
a1aefda860
refactor: use update_response helper in update handlers
Replace duplicated datastar/form/json response branching in brew, cup,
and roast update handlers with the shared update_response helper.
2026-02-13 13:05:50 +00:00
Jon Seager
167af65943
fix: prevent XSS in redirect script via JSON encoding
JSON-encode the URL in render_redirect_script instead of interpolating
it into a single-quoted string, preventing injection via crafted URLs.
2026-02-13 13:05:44 +00:00
Jon Seager
ff2c602139
fix: validate cafe URL scheme on create and update
Make is_valid_url_scheme pub(crate) and use it to filter cafe website
URLs on both create and update, matching roaster validation behavior.
Add UpdateCafe::normalize() method for consistent sanitization.
2026-02-13 13:05:29 +00:00
Jon Seager
b5d5949adf
refactor: simplify admin page auth with direct session lookup
Replace the private extract_user_from_session helper with inline
session/user lookup, reusing the shared SESSION_COOKIE_NAME constant.
Adds tracing instrumentation to the handler.
2026-02-13 13:05:23 +00:00
Jon Seager
0bf30d3475
fix: verify authenticated user in passkey add finish
Add AuthenticatedUser extractor to passkey_add_finish and verify the
session user matches the challenge owner, preventing one user from
completing another user's passkey registration.
2026-02-13 13:05:14 +00:00
Jon Seager
55f2de1e92
refactor: deduplicate SESSION_COOKIE_NAME constant
Make the constant pub(crate) in application::auth and remove the
duplicate definition from routes::app::auth.
2026-02-13 13:04:34 +00:00
Jon Seager
78cc66972d
refactor: extract static_asset! macro for static file handlers
Replace 16 near-identical async functions with a macro that generates
static-asset handlers with embedded content and one-week cache headers.
2026-02-13 13:03:14 +00:00
Ubuntu
7e0b910008
fix: validate CLI callback URLs to localhost-only
Reject non-localhost CLI callback URLs in the WebAuthn auth start
endpoint to prevent token exfiltration to external servers.
2026-02-13 13:01:12 +00:00
Jon Seager
24d9d75145
feat: add home screen icons for iOS and Android 2026-02-13 11:41:16 +00:00
Jon Seager
2e41c28429
fix: apply EXIF orientation to uploaded photos
iPhone photos were displayed rotated because EXIF orientation metadata
was not being applied. Fix both the client-side canvas conversion
(use createImageBitmap which respects EXIF) and the server-side image
processing (read and apply EXIF orientation before resizing).
2026-02-12 18:07:52 +00:00
Jon Seager
3a32d9725a
feat: add remaining amount field to bag edit form 2026-02-11 08:53:56 +00:00
Jon Seager
4190fc2620
feat: log payload fields in trace spans by redacting image data
Add ImageData newtype that wraps Option<String> with a custom Debug impl
showing Some(<image>)/None instead of raw base64. Replace image fields on
all 14 submission structs and remove payload from tracing skip lists so
textual/numeric fields appear in spans.
2026-02-11 08:37:03 +00:00
Jon Seager
a38e9aae5b
fix: use shortened dates in expanded timeline cards 2026-02-10 20:33:24 +00:00
Jon Seager
7ec422f7ce
style: short month names on expanded timeline cards 2026-02-10 20:32:13 +00:00
Jon Seager
2cd579a574
refactor: deduplicate update handlers and edit templates
Add HasChanges trait with impl_has_changes! macro, validate_update()
and update_response() helpers to reduce boilerplate across all 7 entity
update handlers. Extract edit form actions (error, spinner, buttons)
into a shared Askama macro. Also adds missing no-changes validation
to the bag update handler.
2026-02-10 20:04:24 +00:00
Jon Seager
4ee8cf7964
fix: polish edit forms and detail pages
- Skip payload in tracing::instrument to avoid logging base64 image data
- Add blob: to CSP img-src for image preview support
- Add deferred_upload_with_preview macro for edit form image previews
  with Replace/Remove buttons and proper DOM cleanup on replacement
- Fix datastar-fetch finished handler (evt.detail.response is undefined
  for redirect scripts)
- Display brew time in M:SS format on edit form
- Add full-width Save Changes button with check icon and Cancel button
  to all edit forms
- Fix country flag emoji spacing on cafe and cup detail pages
- Add "View on Map" Google Maps link to cafe and cup detail pages
2026-02-10 19:43:03 +00:00
Jon Seager
9650735057
fix: wire edit forms for Datastar submission and recompute bag remaining
Add {contentType: 'form'} to all edit template @put() calls so form
inputs are submitted by name rather than as Datastar signals. Replace
the brew edit quick notes text input with toggle pill buttons. Recompute
bag remaining when amount is updated to account for brew deductions.
Add CLI tests for brew and cup update commands.
2026-02-10 19:43:00 +00:00
Jon Seager
5f99ca0953
feat: add CLI update commands for brews and cups
Add UpdateBrewCommand and UpdateCupCommand to the CLI with all
updatable fields. Add corresponding update() methods to BrewsClient
and CupsClient.
2026-02-10 19:42:55 +00:00
Jon Seager
1a1b28559c
feat: add edit page route handlers, template structs, and edit templates
Add edit page handlers for all 7 entities (roaster, roast, bag, brew,
cafe, cup, gear) with authentication, data pre-loading, and image URL
resolution. Register edit routes in app router. Add corresponding
template structs and HTML templates with pre-populated forms.
2026-02-10 19:42:49 +00:00
Jon Seager
94088f1f4b
feat: add edit button to all detail pages
Add edit_button and edit_delete_buttons macros to detail_cards.html.
All 7 entity detail pages now show an Edit button next to Delete when
authenticated. Each detail template struct receives a pre-computed
edit_url from the route handler.
2026-02-10 19:42:47 +00:00
Jon Seager
1c029f52f4
feat: add update endpoints for brews and cups
Add UpdateBrewSubmission and UpdateCupSubmission types with image
support. Register PUT handlers on /brews/{id} and /cups/{id} with
three-way response pattern (Datastar/form/JSON).
2026-02-10 19:42:45 +00:00
Jon Seager
bc449e520e
feat: upgrade update handlers to FlexiblePayload with three-way response
All five existing update handlers (roaster, roast, cafe, gear, bag) now
accept FlexiblePayload with UpdateSubmission types that separate image
data from domain structs. Each returns Datastar redirect scripts, form
redirects, or JSON depending on request type. Image save support added
to all update paths.
2026-02-10 19:42:43 +00:00
Jon Seager
77cb61f91b
feat: add UpdateBrew, UpdateCup structs and expand UpdateBag
Add domain update structs for brew and cup entities, add update()
methods to their repository traits and SQL implementations, and
expand UpdateBag with roast_id, roast_date, and amount fields.
2026-02-10 19:42:39 +00:00
Jon Seager
fbf5a288c1
feat: support HEIF/HEIC image uploads via client-side Canvas conversion
Convert all image files to JPEG on the client before uploading, allowing
iPhone HEIF/HEIC photos to work without server-side libheif dependency.
2026-02-10 17:56:48 +00:00
Jon Seager
bb4aa103fb
fix: add success logging for deferred image saves
Log at info level when a deferred image is successfully saved, matching
the project convention that every create/update operation logs with
entity context.
2026-02-10 17:50:46 +00:00
Jon Seager
dfd9814fa5
fix: add concurrency semaphore for image processing
Limit concurrent image processing tasks to 4 via a tokio::Semaphore on
AppState. Acquired before spawn_blocking in both upload_image() and
save_deferred_image() to prevent CPU/memory exhaustion from concurrent
image uploads.
2026-02-10 17:50:42 +00:00