No description
Find a file
2025-12-13 14:52:52 +11:00
api Remove email from invites 2025-11-05 20:27:59 +11:00
households Refactor household models and repository queries to use dynamic column selection; remove email from invitation schema in OpenAPI 2025-12-13 14:52:52 +11:00
ingredients Disallow empty ingredients 2025-11-02 18:14:03 +11:00
meals phase complete 2025-11-02 21:33:23 +11:00
products Tighten api endpoints 2025-10-26 15:14:05 +11:00
recipes scraping improvements 2025-11-04 18:31:13 +11:00
scripts scraping improvements 2025-11-04 18:31:13 +11:00
shopping autoformat 2025-11-02 17:51:10 +11:00
tests Scraping tests pass 2025-11-05 19:58:08 +11:00
users Remove cookie auth helpers, clean Person-based recipe function; add indices and test seeding helper; spec updated 2025-11-01 21:28:50 +11:00
.editorconfig Added ruff, black and mypy 2025-10-18 14:26:42 +11:00
.env.example Squashed commit of the following: 2025-10-20 00:12:16 +11:00
.gitignore Squashed commit of the following: 2025-10-19 20:24:23 +11:00
.pre-commit-config.yaml persons removed 2025-11-01 22:35:10 +11:00
common.py Avoid optional arrays 2025-11-01 12:21:12 +11:00
CONTRIBUTING.md Squashed commit of the following: 2025-10-20 00:12:16 +11:00
db.py persons removed 2025-11-01 22:35:10 +11:00
dev-requirements.txt Squashed commit of the following: 2025-10-20 00:12:16 +11:00
main.py phase complete 2025-11-02 21:33:23 +11:00
Makefile Squashed commit of the following: 2025-10-20 00:12:16 +11:00
openapi.json Refactor household models and repository queries to use dynamic column selection; remove email from invitation schema in OpenAPI 2025-12-13 14:52:52 +11:00
plan.md Significant refactor 2024-01-13 12:54:04 +11:00
pyproject.toml Avoid optional arrays 2025-11-01 12:21:12 +11:00
README.md households api cleanup 2025-11-05 20:24:47 +11:00
requirements.txt feat(meals+auth): remove Person from meals v2 DTOs (MemberRef) and adopt Argon2 hashing 2025-11-01 17:00:36 +11:00
security.py format 2025-11-01 15:34:01 +11:00
settings.py URI fixes 2025-11-05 20:07:55 +11:00
transform.sql Squashed commit of the following: 2024-10-13 19:19:57 +11:00
units.py Added ruff, black and mypy 2025-10-18 14:26:42 +11: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, well do the rest.

Why youll 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) - STRICTLY NO SQL AT THIS LAYER!
    • 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 + 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 scripts
    • export_openapi.py — writes openapi.json from the live app
    • manual_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_PRODtrue/false controls frontend proxy vs. static serving (default false)
  • FRONTEND_DEV_URL — dev server to reverse-proxy in non-prod (default http://localhost:8000/)
  • 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 arent provided, deterministic dev secrets are used. Dont 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 features 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:

make openapi

This writes openapi.json at the repo root.


Built with love and leftovers. Hungry for issues and PRs. 🧑‍🍳