6.1 KiB
6.1 KiB
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 /healthzreturns 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/staticsettings.py— Environment-driven runtime settings (no external deps)security.py— Minimal JWT utilities (HS256) + helpersdb.py— aiosqlite connect +create()bootstraps all domain tablesapi/— HTTP surface (versioned under/api/v1) - STRICTLY NO SQL AT THIS LAYER!auth.py— register, login, refresh, logouthouseholds.py— create/list, members, invitations, scoped routesingredients.py— household-scoped NLP parsingrecipes.py— household-scoped list/get/create/delete, public utilitiesmeals.py— household-scoped CRUD + mark consumedshopping.py— household-scoped current list, purchase, request/unrequestopenapi.py— OpenAPI augmentation (cookie auth, problem+json)deps.py— DB/session, auth, household scoping, error helpers
- Domain packages (models + repository + sql mutation + helpers):
persons/users/,households/,ingredients/,recipes/(incl.scraping.py),meals/,products/(Coles/Woolworths helpers),shopping/- Strive to be useful as a fairly portal package independant of the http layer
scripts/— Utility scriptsexport_openapi.py— writesopenapi.jsonfrom the live appmanual_parse_recipes.py— Test parsing against a variety of sources
tests/— API and domain tests with fixtures and sample files
Quickstart
First time setup:
make install
Run the dev server:
make dev
Run tests:
make test
Run all checks (lint, typecheck, tests, format check, OpenAPI export):
make all-checks
Environment
These are read from the environment (see settings.py):
DOOF_DB— SQLite file path (default./data/doof.sqlite)DOOF_PROD—true/falsecontrols frontend proxy vs. static serving (defaultfalse)FRONTEND_DEV_URL— dev server to reverse-proxy in non-prod (defaulthttp://localhost:8000/)DOOF_JWT_ISSUER,DOOF_JWT_AUDIENCE— JWT claimsDOOF_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!)
Dev convenience: if secrets aren’t provided, deterministic dev secrets are used. Don’t ship those.
API surface (v1)
- 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
Errors are consistent Problem Details (application/problem+json). Models serialize in camelCase.
Make targets
make install— Create venv and install dependenciesmake dev— Run dev server (uvicorn main:app --reload)make test— Run tests (pytest -q)make format— Format with ruffmake lint— Lint with ruffmake typecheck— Type check with mypymake openapi— Export OpenAPI toopenapi.jsonmake all-checks— Lint + typecheck + tests + format check + OpenAPI exportmake clean— Remove venv and caches
Development notes
- Schema bootstrap:
db.create(conn)calls each feature’srepository.createto make tables - Frontend integration:
- Dev: requests for non-
/api/*are reverse-proxied toFRONTEND_DEV_URL - Prod: static files served from
./front-dist
- Dev: requests for non-
- 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
OpenAPI
Generate the spec file used by the frontend and CI:
make openapi
This writes openapi.json at the repo root.
Built with love and leftovers. Hungry for issues and PRs. 🧑🍳