2025-11-02 10:41:21 +00:00
## Doof Backend (aka Munch Ease) 🍽️
FastAPI backend for collaborative meal planning, recipe wrangling, and grocery shopping. Household-scoped, JWT-secured, SQLite-fast. Bring your recipes, we’ ll do the rest.
## Why you’ ll love it
- 🚀 Fast and modern API with FastAPI + Pydantic v2
- 🏠 Household-scoped everything (recipes, meals, shopping) for clean multi-user isolation
- 🔐 JWT access tokens + HttpOnly refresh cookie (Argon2 password hashing)
- 🧪 100% test-friendly: ephemeral SQLite, deterministic APIs, RFC7807 errors
- 🧠 Ingredient NLP parsing with product matching
- 🛒 One-click shopping lists: request meals or individual ingredients, dedup done for you
- 🧾 OpenAPI on tap for your frontend and SDKs
- 🔗 Built-in recipe parsing from URLs (with BeautifulSoup + httpx)
## Stack
- Runtime: Python 3.11+
- Web: FastAPI, Starlette
- Data models: Pydantic v2 (camelCase JSON via custom `ApiModel` )
- Database: SQLite (aiosqlite), schema bootstrapped in each `repository.py`
- Auth: Custom HMAC-SHA256 JWTs + Argon2 password hashing
- Parsing/Scraping: `ingredient-parser-nlp` , `beautifulsoup4` , `httpx`
- Tooling: ruff (lint+format), mypy (typecheck), pytest, pre-commit, uvicorn
## Features at a glance
- 🔑 Auth: Register, login, refresh, logout; access bearer token + HttpOnly refresh cookie
- 🏡 Households: Create, list, invite members; accept invitations via shareable links
- 🧾 Recipes: Create, list, paginate, delete (hide) with actor attribution; parse-from-url helper
- 🧪 Ingredients: NLP parse one or many lines; product matching baked in
- 🍽️ Meals: Plan, get, update, delete, mark consumed; validate participants and recipes
- 🛍️ Shopping: Request meals and ingredients, view current list, purchase to a list, fetch past lists
- 🩺 Health: `GET /healthz` returns a tiny “ok” model for probes
- 📜 Errors: RFC7807 Problem Details everywhere, with tidy camelCase payloads
## Project structure
- `main.py` — FastAPI app factory, routers, exception handlers, health, frontend proxy/static
- `settings.py` — Environment-driven runtime settings (no external deps)
- `security.py` — Minimal JWT utilities (HS256) + helpers
- `db.py` — aiosqlite connect + `create()` bootstraps all domain tables
2025-11-05 09:24:47 +00:00
- `api/` — HTTP surface (versioned under `/api/v1` ) - STRICTLY NO SQL AT THIS LAYER!
2025-11-02 10:41:21 +00:00
- `auth.py` — register, login, refresh, logout
- `households.py` — create/list, members, invitations, scoped routes
- `ingredients.py` — household-scoped NLP parsing
- `recipes.py` — household-scoped list/get/create/delete, public utilities
- `meals.py` — household-scoped CRUD + mark consumed
- `shopping.py` — household-scoped current list, purchase, request/unrequest
- `openapi.py` — OpenAPI augmentation (cookie auth, problem+json)
- `deps.py` — DB/session, auth, household scoping, error helpers
2025-11-05 09:24:47 +00:00
- Domain packages (models + repository + sql mutation + helpers):
- `persons/`
2025-11-02 10:41:21 +00:00
- `users/` , `households/` , `ingredients/` , `recipes/` (incl. `scraping.py` ), `meals/` , `products/` (Coles/Woolworths helpers), `shopping/`
2025-11-05 09:24:47 +00:00
- Strive to be useful as a fairly portal package independant of the http layer
- `scripts/` — Utility scripts
- `export_openapi.py` — writes `openapi.json` from the live app
- `manual_parse_recipes.py` — Test parsing against a variety of sources
2025-11-02 10:41:21 +00:00
- `tests/` — API and domain tests with fixtures and sample files
2024-05-23 12:00:13 +00:00
2025-10-19 13:12:16 +00:00
## Quickstart
First time setup:
2025-11-02 10:41:21 +00:00
2025-10-19 13:12:16 +00:00
```bash
make install
```
2025-11-02 10:41:21 +00:00
Run the dev server:
2025-10-19 13:12:16 +00:00
```bash
make dev
```
Run tests:
2025-11-02 10:41:21 +00:00
2025-10-19 13:12:16 +00:00
```bash
make test
```
2025-11-02 10:41:21 +00:00
Run all checks (lint, typecheck, tests, format check, OpenAPI export):
2025-10-19 13:12:16 +00:00
```bash
make all-checks
```
2025-11-02 10:41:21 +00:00
## Environment
2025-10-18 03:26:42 +00:00
2025-11-02 10:41:21 +00:00
These are read from the environment (see `settings.py` ):
2025-10-18 03:26:42 +00:00
2025-11-02 10:41:21 +00:00
- `DOOF_DB` — SQLite file path (default `./data/doof.sqlite` )
- `DOOF_PROD` — `true/false` controls frontend proxy vs. static serving (default `false` )
2025-11-05 09:24:47 +00:00
- `FRONTEND_DEV_URL` — dev server to reverse-proxy in non-prod (default `http://localhost:8000/` )
2025-11-02 10:41:21 +00:00
- `DOOF_JWT_ISSUER` , `DOOF_JWT_AUDIENCE` — JWT claims
- `DOOF_JWT_ACCESS_TTL` , `DOOF_JWT_REFRESH_TTL` — TTLs in seconds (default 900/2592000)
- `DOOF_JWT_ACCESS_SECRET_B64` , `DOOF_JWT_REFRESH_SECRET_B64` — base64 secrets (use in prod!)
2025-10-18 03:26:42 +00:00
2025-11-02 10:41:21 +00:00
Dev convenience: if secrets aren’ t provided, deterministic dev secrets are used. Don’ t ship those.
2025-10-19 13:12:16 +00:00
2025-11-02 10:41:21 +00:00
## API surface (v1)
2025-10-19 13:12:16 +00:00
2025-11-02 10:41:21 +00:00
- Base: `/api/v1`
- Auth: `/auth/register` , `/auth/login` , `/auth/refresh` , `/auth/logout`
- Households: `/households` , `/users/me/households` , `/households/{slug}/members` , `/households/{slug}/whoami` , invitations create/accept
- Ingredients: `/households/{slug}/ingredients/parse` (single or batch parsing)
- Recipes: `/households/{slug}/recipes` (list/paged, create), `/{id}` (get/delete), `/parse-from-url`
- Meals: `/households/{slug}/meals` (create/update/delete/get/upcoming/mark-consumed)
- Shopping: `/households/{slug}/shopping/current` , `/{listId}` , request/unrequest meals and ingredients, purchase lists
- Health: `/healthz`
2024-05-23 12:00:13 +00:00
2025-11-02 10:41:21 +00:00
Errors are consistent Problem Details (`application/problem+json`). Models serialize in camelCase.
2025-10-18 03:26:42 +00:00
2025-11-02 10:41:21 +00:00
## Make targets
2025-10-19 13:12:16 +00:00
2025-11-02 10:41:21 +00:00
- `make install` — Create venv and install dependencies
- `make dev` — Run dev server (`uvicorn main:app --reload`)
- `make test` — Run tests (pytest -q)
- `make format` — Format with ruff
- `make lint` — Lint with ruff
- `make typecheck` — Type check with mypy
- `make openapi` — Export OpenAPI to `openapi.json`
- `make all-checks` — Lint + typecheck + tests + format check + OpenAPI export
- `make clean` — Remove venv and caches
2025-10-19 13:12:16 +00:00
2025-11-02 10:41:21 +00:00
## Development notes
2025-10-19 13:12:16 +00:00
2025-11-02 10:41:21 +00:00
- Schema bootstrap: `db.create(conn)` calls each feature’ s `repository.create` to make tables
- Frontend integration:
- Dev: requests for non-`/api/*` are reverse-proxied to `FRONTEND_DEV_URL`
- Prod: static files served from `./front-dist`
- Security: Argon2 password hashing; HS256 JWTs signed with your secrets; refresh token stored as HttpOnly cookie
- DX niceties: camelCase JSON by default, strict validation, helpful error messages
2025-10-18 03:26:42 +00:00
2025-11-02 10:41:21 +00:00
## OpenAPI
2025-10-18 03:26:42 +00:00
2025-11-02 10:41:21 +00:00
Generate the spec file used by the frontend and CI:
2025-10-19 13:12:16 +00:00
```bash
2025-11-02 10:41:21 +00:00
make openapi
2025-10-18 05:44:36 +00:00
```
2025-11-02 10:41:21 +00:00
This writes `openapi.json` at the repo root.
2025-10-18 05:44:36 +00:00
2025-11-02 10:41:21 +00:00
---
2025-10-18 05:44:36 +00:00
2025-11-02 10:41:21 +00:00
Built with love and leftovers. Hungry for issues and PRs. 🧑🍳