No description
  • Python 46.8%
  • JavaScript 45%
  • CSS 6.8%
  • Dockerfile 1%
  • HTML 0.2%
  • Other 0.2%
Find a file
Brian 9374d9fee6
All checks were successful
Build and Deploy / build-backend (push) Successful in 8s
Build and Deploy / build-frontend (push) Successful in 9s
Build and Deploy / deploy (push) Successful in 5s
Admins, cookbooks, and cook-for-the-group search
Admins:
- Add is_admin to users (idempotent SQLite migration + startup bootstrap that
  promotes the first user; first registrant on a fresh DB also bootstraps).
- /admin/users to list/create users and promote/demote (guards last admin).
- Admins can set any user's reaction on a recipe (reaction endpoint user_id).

Search by who likes/dislikes:
- GET /recipes gains liked_by (all must favorite), not_disliked_by (exclude),
  and disliked_by filters; UI "Who likes it" people picker.

Cookbooks:
- Per-user private cookbooks (cookbooks + cookbook_recipes tables) with manual
  entries and/or smart tag rules (any/all) resolved at query time.
- CRUD + add/remove recipe endpoints; Cookbooks list/detail pages and an
  "Add to cookbook" control on recipes.

Also: shared RecipeCard component, /users directory for pickers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 11:46:53 -06:00
.forgejo/workflows ci: surface Portainer error body in deploy step 2026-06-06 00:00:34 -06:00
backend Admins, cookbooks, and cook-for-the-group search 2026-06-06 11:46:53 -06:00
frontend Admins, cookbooks, and cook-for-the-group search 2026-06-06 11:46:53 -06:00
.env.example Initial commit: recipe manager app 2026-06-05 23:28:54 -06:00
.gitignore Initial commit: recipe manager app 2026-06-05 23:28:54 -06:00
docker-compose.override.yml fix(deploy): make compose image-only for Portainer; move build to override 2026-06-06 00:03:25 -06:00
docker-compose.yml fix(deploy): make compose image-only for Portainer; move build to override 2026-06-06 00:03:25 -06:00
README.md Admins, cookbooks, and cook-for-the-group search 2026-06-06 11:46:53 -06:00

🍳 Recipe Manager

A self-hosted recipe manager: scan recipes from photos, store them in a local SQLite database, scale ingredients by servings, organize with tags, and push shopping lists to Home Assistant.

  • Backend — Python / FastAPI + SQLAlchemy + SQLite (backend/)
  • Frontend — React + Vite single-page app (frontend/)

Features

  • 📷 Scan a recipe from a photo — pluggable OCR: uses Claude vision (best accuracy) when an API key is set, otherwise falls back to local Tesseract OCR with heuristic parsing.
  • 🗂️ Source tracking — note where a recipe came from (family, cookbook, website, …) and the specific name (e.g. "Grandma", "Joy of Cooking").
  • 🔢 Servings & scaling — set base servings; ingredient quantities scale up or down on the fly, with friendly fractions (2 1/4 cups).
  • 🛒 Export to Home Assistant — push the (scaled) ingredient list to a to-do / shopping-list entity via the HA REST API. Also a one-click "copy list".
  • 👥 Multiple users — register / log in (JWT). Recipes are shared: everyone can see and cook every recipe, but only the user who added one can edit or delete it. Each user has their own Home Assistant connection settings.
  • ❤️ Favorites & dislikes — each user can favorite recipes (with a Favorites filter) or mark ones they won't eat; a recipe shows who's a fan and who isn't.
  • 🧑‍🤝‍🧑 Cook-for-the-group search — find recipes that a chosen set of people all like and that nobody in another set dislikes (e.g. "a dinner Garnet, Malea, and Wen all like").
  • 📒 Cookbooks — each user can create any number of named cookbooks and add recipes by hand and/or via smart tag rules (auto-include everything matching the tags, "any" or "all"). Cookbooks are private to their owner.
  • 🛡️ Admins — the first account is an admin and can create other users, promote/demote admins, and set any user's like/dislike on a recipe.
  • 🖼️ Photo uploads — attach multiple photos to each recipe.
  • 🏷️ Tags / categories — free-form keywords (breakfast, dessert, sandwich…) with click-to-filter on the recipe list.

Quick start

1. Backend

cd backend
# Use the existing project venv (../.venv) or create one:
#   python3 -m venv ../.venv
../.venv/bin/python -m pip install -r requirements.txt

cp .env.example .env        # a SECRET_KEY is generated for you on first run if unset
# (optional) set ANTHROPIC_API_KEY in .env to enable the Claude scanner

./run.sh                    # serves on http://localhost:8000  (docs at /docs)

The SQLite database (recipes.db) and uploads/ directory are created automatically on first run.

2. Frontend

cd frontend
npm install
npm run dev                 # serves on http://localhost:5173

Open http://localhost:5173, create an account, and start adding recipes. In dev, Vite proxies API paths to the backend on :8000, so there's nothing else to configure.

Recipe scanning backends

The scanner is chosen by SCAN_BACKEND in backend/.env:

Value Behavior
auto Claude if ANTHROPIC_API_KEY is set, otherwise Tesseract
claude Force the Claude vision model (SCAN_MODEL, default Opus 4.8)
tesseract Force local OCR
  • Claude: just set ANTHROPIC_API_KEY. Returns fully structured fields (title, ingredients split into qty/unit/name, instructions, servings, tags).
  • Tesseract: install the system binary (sudo pacman -S tesseract tesseract-data-eng, apt install tesseract-ocr, or brew install tesseract). Raw text is parsed heuristically — expect to tidy up fields.

If neither is available, scanning returns a clear error and you can still enter recipes by hand. Check GET /api/health to see which backends are live.

Home Assistant export

In Settings, enter:

  • Base URL — e.g. http://homeassistant.local:8123
  • Token — a long-lived access token (HA profile → Long-Lived Access Tokens)
  • Entity — your list entity, e.g. todo.shopping_list

Then on any recipe, pick the servings and click → Home Assistant. Each ingredient is added via the todo.add_item service.

Docker

Both services are containerized. The frontend image runs nginx, which serves the built SPA and reverse-proxies /api and /uploads to the backend — so the whole app is reached on a single port.

cp .env.example .env          # set SECRET_KEY (and ANTHROPIC_API_KEY if wanted)
docker compose up --build     # UI on http://localhost:8080
  • The backend image bundles Tesseract (with English data), so the offline scanner works out of the box; set ANTHROPIC_API_KEY to use Claude instead.
  • SQLite DB and uploaded photos persist in the recipe-data named volume (mounted at /data).
  • FRONTEND_PORT controls the published port (default 8080).

Deploy (Forgejo Actions + Portainer)

.forgejo/workflows/build-deploy.yml builds and pushes both images to git.thenymans.com on every push to main, then redeploys a Portainer stack, pinning the images to the commit SHA (IMAGE_TAG). It reuses the env vars already set on the Portainer stack, so secrets never need to be mirrored into CI.

Images are published as:

git.thenymans.com/brian/recipie-backend:{latest, <sha>}
git.thenymans.com/brian/recipie-frontend:{latest, <sha>}

If your repo owner is not brian, change NAMESPACE in the workflow and IMAGE_NAMESPACE in docker-compose.yml to match.

Required secrets on the Forgejo repo:

Secret Purpose
REGISTRY_USER registry login user
REGISTRY_PASSWORD registry login token/password
PORTAINER_TOKEN Portainer API key
PORTAINER_STACK_ID id of the stack to redeploy
PORTAINER_ENDPOINT_ID Portainer environment/endpoint id

On the Portainer stack, set the runtime env (SECRET_KEY, CORS_ORIGINS, ANTHROPIC_API_KEY, FRONTEND_PORT, …). The workflow only overrides IMAGE_TAG; everything else is preserved across deploys.

Project layout

backend/
  app/
    main.py            FastAPI app + static uploads mount
    models.py          SQLAlchemy models (User, Recipe, Ingredient, Photo, Tag)
    schemas.py         Pydantic request/response models
    security.py        PBKDF2 password hashing + JWT
    scaling.py         servings scaling with fraction formatting
    crud.py            persistence + serialization helpers
    scanning/          pluggable Claude / Tesseract scanners + parser
    routers/           auth, recipes, scan, tags, export
frontend/
  src/
    api.js             fetch client
    auth.jsx           auth context (token in localStorage)
    pages/             Login, Recipes, RecipeDetail, RecipeForm, Settings

Notes

  • Tables are created automatically via Base.metadata.create_all. For schema changes over time, introduce Alembic migrations.
  • Passwords are hashed with PBKDF2-HMAC-SHA256 (stdlib, no native deps).
  • Set a strong SECRET_KEY and serve over HTTPS before exposing this beyond your LAN.