- Go 52.7%
- TypeScript 39.7%
- CSS 7%
- Shell 0.3%
- JavaScript 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks failed
CI / backend (push) Failing after 11m33s
CI / frontend (push) Successful in 3m1s
CI / production-compose (push) Successful in 5s
CI / publish-images (./backend, tidy-cook-backend) (push) Has been skipped
CI / publish-images (./deploy/backup, tidy-cook-backup) (push) Has been skipped
CI / publish-images (./frontend, tidy-cook-frontend) (push) Has been skipped
|
||
| .github/workflows | ||
| backend | ||
| deploy | ||
| docs | ||
| frontend | ||
| .dockerignore | ||
| .env.production.example | ||
| .gitignore | ||
| CLAUDE.md | ||
| docker-compose.production.yml | ||
| docker-compose.yml | ||
| IMPORTER_V3_FOUNDATION_ARCHIVE.md | ||
| PRODUCTION_CONFIG.md | ||
| PRODUCTION_DATA_MODEL.md | ||
| PRODUCTION_DEPLOYMENT_PANGOLIN.md | ||
| PRODUCTION_FRONTEND_AUDIT.md | ||
| PRODUCTION_JOBS.md | ||
| PRODUCTION_OPERATIONS_KOMODO.md | ||
| PRODUCTION_RELEASE_CHECKLIST.md | ||
| PRODUCTION_RELEASE_RUNBOOK.md | ||
| PRODUCTION_RESTORE_DRILL.md | ||
| PRODUCTION_ROUTE_MATRIX.md | ||
| PRODUCTION_SECRET_POLICY.md | ||
| PRODUCTION_SECRET_ROTATION.md | ||
| PRODUCTION_SMOKE_TESTS.md | ||
| PRODUCTION_TASKS.md | ||
| README.md | ||
The Tidy Cook (React + Go)
A full-stack recipe database application with:
- React frontend (modern recipe-book UI) exposed on
http://localhost:3500 - Go backend API with SQLite persistence
- Normalized ingredient catalog with metric-first unit normalization
- Searchable recipes and Gousto-style recipe detail view
- Optional CoFID-powered nutrition calculations per recipe
Stack
- Frontend: React + Vite + TypeScript + CSS
- Backend: Go + Chi + SQLite
- Containerization: Docker + Docker Compose
Data model
The backend creates normalized tables automatically:
units: conversion metadata (g,kg,ml,l,tsp,tbsp,pc)ingredients: normalized ingredient catalog with base unitrecipes: recipe metadatarecipe_steps: ordered instructionsrecipe_ingredients: display quantity + normalized/base quantitycofid_foods: imported CoFID food-code catalogcofid_nutrients: nutrient values per 100g foodingredient_nutrition_map: ingredient to CoFID match cache
If a recipe uses an ingredient not in the catalog, it can be typed manually and is auto-added. Ingredient categories are normalized against a backend-owned canonical vocabulary so seeded ingredients, manual edits, and recipe imports stay aligned.
Run with Docker
docker compose up --build
Then open:
- Frontend:
http://localhost:3500
API endpoints
- Session authentication is required for application endpoints unless an endpoint is explicitly described as public.
- Administrative endpoints require an authenticated user with the
adminrole. GET /api/healthGET /api/readyGET /api/auth/mePOST /api/auth/loginPOST /api/auth/logoutGET /api/ingredients?query=GET /api/ingredients/categoriesPOST /api/ingredientsGET /api/recipes?query=(single search across title, tags, ingredients)GET /api/recipes/{id}GET /api/recipes/{id}/nutrition?servings=POST /api/recipesPUT /api/recipes/{id}DELETE /api/recipes/{id}POST /api/images(upload local recipe image, returns/uploads/...URL)POST /api/pantry/process(session-authenticated; accepts JSON like{"prompt":"add 2 milk","use_ai":true}, form fields likeinput=..., or rawtext/plaininput)POST /api/shopping/voice(dedicatedshopping:addbearer token; accepts JSON like{"utterance":"two litres of milk and six eggs"})GET|POST|DELETE /api/settings/siri-token(session-authenticated status, generate/regenerate, and revoke operations; the raw token is returned only when generated)GET /api/backup/export(admin session, or theADMIN_API_TOKENmachine token used by the scheduled backup; download backup archive)POST /api/backup/import(admin session only; restore from backup archive. Writes a copy of the live database todata/pre-restore/first)POST /api/week-rollover/undo({"kind":"shopping|pantry","start_date":"YYYY-MM-DD"}: take back what was carried into the current week)POST /api/carryover/keep({"kind":"shopping|pantry","id":123}: restart the stale clock on an item you still want)POST /api/import-sessions/{id}/retry(restart a failed import from its saved source; body{"use_ai": false}retries without AI)GET /api/auth/sessions,DELETE /api/auth/sessions/{id},POST /api/auth/sessions/revoke-others(list and sign out the caller's own devices; sessions also expire afterSESSION_IDLE_TTL, default 7 days idle)POST /api/nutrition/cofid/import(upload CoFID.xlsx)GET /api/nutrition/statusPOST /api/admin/review-recipes(normalize all or selected recipes, optional nutrition refresh)POST /api/admin/openfoodfacts/sync(import OFF ingredient/category/label references)GET /api/admin/openfoodfacts/status
Backup format
The Tidy Cook backups use .recipobak (ZIP archive) with:
manifest.json:format: "recipopedia-backup"version: "2.0"exported_at(RFC3339 UTC)
database.v2.json: complete allowlisted durable-table dump, including accounts, settings, recipes, planning, shopping, pantry, imports, nutrition, and barcode datauploads/: all locally stored recipe images
Rebuildable reference data is not exported: the Open Food Facts taxonomy (taxonomy_terms), the CoFID tables and the ingredient nutrition match cache. After restoring onto a fresh instance, re-upload the CoFID .xlsx and run the Open Food Facts sync if you use them. Archives made by older releases that include those tables still restore.
Active sessions and one-time password-reset tokens are intentionally excluded. Restoring a backup invalidates existing sessions while preserving user accounts and password hashes. Version 1 full-backup archives are rejected because they do not contain all durable application domains.
Local development (without Docker)
Backend
cd backend
go mod tidy
go run .
Frontend
cd frontend
npm install
npm run dev
Frontend dev server runs on port 3500.
Generate Ingredient Seed Candidates
You can generate a ranked ingredient seed candidate file from the current SQLite database:
cd backend
go run ./scripts/generate_ingredient_seed.go \
--db ./data/recipopedia.db \
--output ./seeds/ingredients.generated.json \
--version ingredients.generated.v1 \
--limit 500
Useful flags:
--min-recipes 2: only include ingredients used in at least 2 distinct recipes--include-unused: include ingredients even if they have no recipe usage yet--include-custom=false: exclude ingredients currently categorized ascustom--include-supermarket: includepreferred_supermarketin the generated seed
The output schema matches the bundled ingredient seed format used at startup.
Ingredient Seed Releases
Installation-time ingredient population is driven by bundled seed files in backend/seeds.
Startup behavior:
- the backend scans bundled
ingredients.vN.jsonfiles - the highest bundled version is selected automatically
- that seed is applied once per database via
seed_versions - later startups are idempotent for the same seed version
Recommended release workflow for a larger default catalog:
- Generate a candidate file, for example
ingredients.v2.json. - Review and curate canonical names, categories, units, aisles, and aliases.
- Commit the new seed file into
backend/seeds/. - Release the app. Fresh installs will pick the highest bundled seed automatically.
- Existing installs will apply the new version once on next startup because
seed_versionstracks applied versions.
This keeps installation offline and deterministic: the app does not need to fetch USDA or Open Food Facts during install.
Production deployment
The product name is The Tidy Cook; recipopedia remains the repository and
legacy internal data-format identifier. The first production target is a single Linux host using Docker Compose with
Pangolin as the authenticated HTTPS entrypoint. See
PRODUCTION_DEPLOYMENT_PANGOLIN.md
for the Newt-site and same-host local-site layouts.
Successful main CI runs publish attested linux/amd64 and linux/arm64
backend, frontend, and backup images under ghcr.io/wantxapas/tidy-cook-*.
Production selects an immutable sha-<full-commit> image tag for deployment
and rollback.
For the complete version bump, validation, image verification, GitHub release, deployment, and rollback sequence, see PRODUCTION_RELEASE_RUNBOOK.md.
Komodo scheduling, encrypted SSH/NAS backups, and Uptime Kuma monitoring are covered in PRODUCTION_OPERATIONS_KOMODO.md.
The v1 authorization and ownership boundary is documented in PRODUCTION_DATA_MODEL.md.
Shopping list and pantry: carrying items into a new week
Shopping items and pantry stock belong to a planning week. The first time the current week is opened, what was not finished is carried into it once:
- Shopping: manual items that were not bought (added in the app, through the
API, the Siri shortcut or a suggestion) move on unchanged. Bought and removed
items stay behind. Manual items show a Manual label and, when they came from
elsewhere, via Siri / API / Telegram;
added_viais set by the server and an API client may send"added_via":"api"when adding an item. - Pantry: stock moves on minus whatever that week's planned recipes use, so 500 g of flour with 200 g planned carries 300 g; stock nothing planned uses carries whole; stock the plan used up does not carry.
- Only the current week is ever rolled into: a future week cannot pull an item forward while it can still be bought this week, and a past week is history. Each source item is stamped once carried, so re-opening never duplicates it and a deleted copy never comes back.
- Carried items show Carried from 12 Oct. After 4 weeks an item is flagged Still needed? (it keeps rolling) with a Keep them action, and editing an item also restarts the clock. A banner above the list summarises the carry and offers Undo. Undo removes the copies (taking back any quantity merged into an existing line) and leaves the originals in their old week.