142 lines
5.9 KiB
Markdown
142 lines
5.9 KiB
Markdown
## 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
|
||
- `api/` — HTTP surface (versioned under `/api/v1`)
|
||
- `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
|
||
- Domain packages (models + repository + helpers):
|
||
- `users/`, `households/`, `ingredients/`, `recipes/` (incl. `scraping.py`), `meals/`, `products/` (Coles/Woolworths helpers), `shopping/`
|
||
- `scripts/export_openapi.py` — writes `openapi.json` from the live app
|
||
- `tests/` — API and domain tests with fixtures and sample files
|
||
|
||
## Quickstart
|
||
|
||
First time setup:
|
||
|
||
```bash
|
||
make install
|
||
```
|
||
|
||
Run the dev server:
|
||
|
||
```bash
|
||
make dev
|
||
```
|
||
|
||
Run tests:
|
||
|
||
```bash
|
||
make test
|
||
```
|
||
|
||
Run all checks (lint, typecheck, tests, format check, OpenAPI export):
|
||
|
||
```bash
|
||
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/false` controls frontend proxy vs. static serving (default `false`)
|
||
- `FRONTEND_DEV_URL` — dev server to reverse-proxy in non-prod (default `http://localhost:8080/`)
|
||
- `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!)
|
||
|
||
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 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
|
||
|
||
## Development notes
|
||
|
||
- 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
|
||
|
||
## OpenAPI
|
||
|
||
Generate the spec file used by the frontend and CI:
|
||
|
||
```bash
|
||
make openapi
|
||
```
|
||
|
||
This writes `openapi.json` at the repo root.
|
||
|
||
---
|
||
|
||
Built with love and leftovers. Hungry for issues and PRs. 🧑🍳
|