Commit graph

554 commits

Author SHA1 Message Date
Jon Seager
08255a8515 chore: clean up stale Nix references 2026-05-22 14:59:39 +00:00
dependabot[bot]
c8d7333f7a
build(deps): bump the cargo-deps group across 1 directory with 8 updates (#23)
Bumps the cargo-deps group with 8 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [axum](https://github.com/tokio-rs/axum) | `0.8.8` | `0.8.9` |
| [image](https://github.com/image-rs/image) | `0.25.9` | `0.25.10` |
| [open](https://github.com/Byron/open-rs) | `5.3.3` | `5.3.5` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.149` | `1.0.150` |
| [tracing-subscriber](https://github.com/tokio-rs/tracing) | `0.3.22` | `0.3.23` |
| [tower-http](https://github.com/tower-rs/tower-http) | `0.6.8` | `0.6.11` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.21.0` | `1.23.1` |
| [once_cell](https://github.com/matklad/once_cell) | `1.21.3` | `1.21.4` |



Updates `axum` from 0.8.8 to 0.8.9
- [Release notes](https://github.com/tokio-rs/axum/releases)
- [Changelog](https://github.com/tokio-rs/axum/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/axum/compare/axum-v0.8.8...axum-v0.8.9)

Updates `image` from 0.25.9 to 0.25.10
- [Changelog](https://github.com/image-rs/image/blob/v0.25.10/CHANGES.md)
- [Commits](https://github.com/image-rs/image/compare/v0.25.9...v0.25.10)

Updates `open` from 5.3.3 to 5.3.5
- [Release notes](https://github.com/Byron/open-rs/releases)
- [Changelog](https://github.com/Byron/open-rs/blob/main/changelog.md)
- [Commits](https://github.com/Byron/open-rs/compare/v5.3.3...v5.3.5)

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `tracing-subscriber` from 0.3.22 to 0.3.23
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.22...tracing-subscriber-0.3.23)

Updates `tower-http` from 0.6.8 to 0.6.11
- [Release notes](https://github.com/tower-rs/tower-http/releases)
- [Commits](https://github.com/tower-rs/tower-http/compare/tower-http-0.6.8...tower-http-0.6.11)

Updates `uuid` from 1.21.0 to 1.23.1
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.21.0...v1.23.1)

Updates `once_cell` from 1.21.3 to 1.21.4
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.21.3...v1.21.4)

---
updated-dependencies:
- dependency-name: axum
  dependency-version: 0.8.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-deps
- dependency-name: image
  dependency-version: 0.25.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-deps
- dependency-name: open
  dependency-version: 5.3.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-deps
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-deps
- dependency-name: tracing-subscriber
  dependency-version: 0.3.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-deps
- dependency-name: tower-http
  dependency-version: 0.6.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-deps
- dependency-name: uuid
  dependency-version: 1.23.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-deps
- dependency-name: once_cell
  dependency-version: 1.21.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-22 14:27:23 +01:00
Jon Seager
6766aa16f2 ci: bump jdx/mise-action from v2 to v4 2026-05-22 13:18:31 +00:00
Jon Seager
cbc741b229 build(deps): bump cargo dependencies from dependabot PRs
Cherry-pick dependency updates from dependabot PRs #16, #18, #19, #21:
- clap 4.5 → 4.6
- tokio 1.38 → 1.52
- rand 0.9 → 0.10 (breaking: RngCore → Rng)
- sha2 0.10 → 0.11
- tempfile 3.8 → 3.27
- once_cell 1.19 → 1.21
- docker/login-action v3 → v4 (already on branch)

Fix rand 0.10 breaking change: RngCore trait renamed to Rng.
2026-05-22 13:07:22 +00:00
Jon Seager
616876e73f fix(ci): Install mold linker and rustfmt in deploy workflow
The Deploy workflow on main failed because .cargo/config.toml requires
the mold linker, but deploy.yml only installed lld. The earlier fix
commit only updated push.yml, missing deploy.yml.
2026-05-22 13:05:51 +00:00
Jon Seager
46b0cb67ef fix(ci): Install mold linker and rustfmt component for CI
Add mold to system deps (needed by .cargo/config.toml linker setting)
and explicitly install rustfmt/clippy components since mise-action
doesn't always install Rust components.
2026-05-22 12:51:34 +00:00
Jon Seager
b55e172d72 chore: Remove djlint from project
djlint corrupts Askama/Jinja templates and cannot be used safely.
Remove .djlintrc, install-py-deps task, and all djlint references
from prek.toml. Restore full README.md that was lost during rebase.
2026-05-22 11:06:18 +00:00
Jon Seager
74e3b600fc chore: Switch e2e tests to Chrome for Testing via mise
Replace snap-installed Chromium/chromedriver with Chrome for Testing
installed via npx @puppeteer/browsers. Binaries live in
$MISE_DATA_DIR/chrome-for-testing/ with version-agnostic path lookup.

Add install-e2e mise task and required apt packages to README and CI.
2026-05-22 12:03:03 +01:00
Jon Seager
b1e0a41797 chore: Switch to mold linker and fix Dockerfile build
Use mold linker in .cargo/config.toml instead of lld. Add binutils
and mkdir -p to Dockerfile builder stage. Update fly.toml model.
2026-05-22 12:03:03 +01:00
Jon Seager
197f0f541b chore: Simplify formatting toolchain
Remove treefmt and djlint from pre-commit hooks. djlint corrupted
Askama template syntax on first run. Use individual formatters
(cargo fmt, shfmt, oxfmt) directly instead. Update prek hooks
to use fmt-rs and fmt-sh separately.
2026-05-22 12:03:03 +01:00
Jon Seager
527358b695 chore: migrate from Nix to mise for dev environment management
Replace Nix flake with mise for toolchain management, treefmt for
formatting, prek for pre-commit hooks, and a Dockerfile for container
builds. Update CI workflows to use jdx/mise-action instead of Nix.

Add system dependency instructions to README.md and CLAUDE.md.
2026-05-22 12:03:03 +01:00
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
ee145fffe6
feat: add share button to all detail pages
Copy current URL to clipboard with toast feedback on brew, roast,
roaster, bag, cup, cafe, and gear detail pages.
2026-02-22 10:17:14 +00:00
Jon Seager
7b9cf8147e
fix: stabilize flaky timeline CLI test under parallel execution
Fetch timeline with page_size=all so concurrent test data from other
CLI tests doesn't push events off page 1, causing assertion failures.
2026-02-22 10:03:56 +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
488e7f9d74
build: add .envrc for direnv integration 2026-02-15 13:51:25 +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
bfe4f7d67d
ci: speed up nix container build with crane dependency caching
Replace rustPlatform.buildRustPackage with crane to split the Rust
compilation into a deps-only derivation (keyed on Cargo.lock) and a
source derivation. The deps derivation is cached in the Nix store
between CI runs, avoiding full recompilation on every deploy.
2026-02-15 12:29:06 +00:00
Jon Seager
1075986554
fix: use form element for registration page
The formatter converted the register container from a div with onclick
to a semantic form with onsubmit, improving accessibility and enabling
native form submission behavior.
2026-02-15 11:39:42 +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
02235426c4
ci: speed up pipeline with cargo caching and merged jobs
Merge separate lint and test jobs into a single check job so clippy's
compilation artifacts are reused by tests. Add actions/cache for the
Cargo target directory and registry to enable incremental builds across
runs. Drop unnecessary fetch-depth: 0 from CI jobs.
2026-02-15 11:29:45 +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
ae016c6437
build: add LD_LIBRARY_PATH to devshell for libssl 2026-02-15 10:36:43 +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
36e2d90ee8
refactor: document content|safe safety invariant in templates
Add comments explaining why the |safe filter is used in data.html and
stats.html — content is always pre-rendered HTML from render_template().
2026-02-13 16:20:25 +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
590868e51f
test: add integration tests for webauthn, passkeys, and input validation
Add webauthn API tests (register, auth, passkey add flows), passkey
endpoint auth tests, bag validation (zero/negative amount, invalid date),
brew validation (zero weight, negative grind, zero water, temp > 100),
and fix flaky date assertion in bag close test.
2026-02-13 13:06:27 +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