- Rust 76%
- TypeScript 14.4%
- CSS 7.2%
- JavaScript 2.1%
- Just 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .woodpecker | ||
| ci | ||
| e2e | ||
| migrations | ||
| src | ||
| static | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| justfile | ||
| README.md | ||
Sustenance
A small shared grocery list built with Rust, Axum, Maud, htmx, WebSockets, and SQLite.
Run locally
cargo run
Open http://localhost:3000. The application creates sustenance.db in the
working directory on first start.
Hot reload (development)
For development, just dev (the default just recipe) watches src/ and
rebuilds + restarts the server on every change, so you don't have to stop and
restart manually. It uses cargo-watch:
cargo install cargo-watch # one-time install (not in the Debian apt repos)
just dev # or just `just`
Edits to the server-rendered HTML templates (in src/views.rs) and Rust code
trigger a rebuild and restart. Each restart drops in-memory state (sessions,
WebSocket connections); connected clients auto-reconnect. Note that the static
assets under static/ are embedded into the binary at compile time, so CSS/JS
changes also trigger a rebuild.
Note: use localhost (not 127.0.0.1) when testing passkeys locally —
browsers reject IP addresses as WebAuthn RP IDs. The app defaults to
localhost for loopback hosts, so passkeys work out of the box when you access
the site via http://localhost:3000.
Configuration
| Variable | Default | Purpose |
|---|---|---|
DATABASE_PATH |
sustenance.db |
SQLite database path |
BIND_ADDRESS |
127.0.0.1:3000 |
Listen address |
PUBLIC_BASE_URL |
derived from BIND_ADDRESS |
Base URL used in invitation links |
COOKIE_SECURE |
false |
Add the Secure attribute to session cookies |
REGISTRATION_MODE |
invite_only |
Use open for local development; otherwise registration requires a valid list invitation after the first account |
SEED_CONFIG |
seed.json |
Optional JSON file with a default user to create when the database is first initialized |
RP_ID |
derived from PUBLIC_BASE_URL |
WebAuthn relying party ID (the host users access the site from) |
RP_NAME |
Sustenance |
WebAuthn relying party name shown to users |
RUST_LOG |
sustenance=info,tower_http=info |
Log filter; HTTP requests are logged at info level |
Seeding a default user
If a JSON config file exists at the path given by SEED_CONFIG (default seed.json),
Sustenance creates the configured user on startup when the database has no users yet.
The file is optional — if it is missing or invalid, seeding is silently skipped.
{
"user": {
"email": "you@example.com",
"display_name": "You",
"password": "a-strong-password"
}
}
Current features
- Email/password accounts with Argon2 password hashes
- Optional WebAuthn passkeys for passwordless sign-in (managed from the account page)
- Cookie-backed sessions and CSRF tokens for list mutations
- Shared lists with one-time, seven-day invitation links
- Invite-only registration by default after the first account
- Add, edit, check, and delete grocery items
- Global categories with common defaults seeded at startup and custom category creation
- Items grouped by category and assigned from the add/edit forms
- Meals with ingredients, markdown descriptions, and one-click "add meal to list"
- Rewards cards with store name and number, shared across all users and rendered as scannable Code 128 / Code 39 barcodes
- Single-card scan view that shows one barcode at a time and keeps the screen awake for scanning
- Server-authoritative last-write-wins updates
- Per-list WebSocket updates with server-rendered htmx fragments
- In-memory presence for members currently viewing a list
- Responsive layout for phone, tablet, and desktop
The htmx scripts are currently loaded from unpkg. They can be vendored into static/ before production deployment.
Verification
cargo fmt --all -- --check
cargo check
cargo test
End-to-end tests (Playwright)
The e2e tests live in e2e/ and use Playwright with a real browser. Each test
starts its own server against a fresh, throwaway database on a unique port, so
tests are fully isolated from each other and from your real sustenance.db.
The tests launch target/debug/sustenance, so build the server first:
# one-time setup
cargo build
cd e2e
npm install
npx playwright install chromium
# run the tests (each test launches its own server against a fresh DB)
cd e2e
npx playwright test