Files
Kochbuch/CLAUDE.md
T
Kyle DammannandClaude Opus 5.5 9256e94286 Add phase 3: comments and cooking mode
- Comments on visible recipes; author and admin may delete; per-user
  hourly rate limit (COMMENT_RATE_LIMIT_PER_HOUR)
- Cooking mode with large text, tick-off checklists, serving scaling and
  Screen Wake Lock to keep the display on
- Serving calculator auto-submits on change
- Privacy tests cover comment and cooking-mode URLs

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:39:15 +01:00

4.4 KiB
Raw Blame History

Kochbuch – Notes for Claude

Self-hosted recipe web app for a homelab. Runs via docker compose behind the owner's existing Nginx + Cloudflare (public URL: https://kochbuch.dammann.vip). The app itself serves plain HTTP on one port; TLS, domain and proxy are out of scope.

Status

  • Phase 1: Docker setup, DB, auth with invitations, roles
  • Phase 2: Recipes CRUD, visibility (private/public), photos, notes, search, favourites
  • Phase 3: Comments, serving calculator (server-side ?portionen=), cooking mode (wake lock)
  • Phase 4: AI (OpenAI): clean up manual input + photo/screenshot import
  • Phase 5: Link import (schema.org JSON-LD, Instagram with paste fallback, Android PWA share target)
  • Phase 6: Backup script, full README

Stack

Django 5.2 LTS (Python 3.12), server-rendered templates, PostgreSQL 17, gunicorn (gthread), WhiteNoise for static files, Pillow + pillow-heif for photos. No Redis, no HTMX, no JS build step: plain forms with small progressive enhancements in static/js/app.js.

Layout

  • config/ – settings (all env-driven, see .env.example), urls, wsgi. settings_test.py for pytest.
  • core/ – cross-cutting: ProxyMiddleware (trusted proxies, client IP), SecurityHeadersMiddleware (CSP, Permissions-Policy, no-store for logged-in pages), DB-backed rate limiter (core/ratelimit.py), /healthz, error pages.
  • accounts/ – custom User (e-mail login, role = user|admin), Invitation, PasswordResetToken, login/registration/reset views, admin area (admin_views.py, /verwaltung/…), ensure_admin management command (first admin from env).
  • recipes/ – Recipe (+ Ingredient, Step, Tag, RecipeImage, Favorite, Comment). RecipeQuerySet.visible_to/ editable_by/deletable_by/search hold all access rules. parsing.py converts the text form fields (one ingredient per line, Gruppe: headers, steps separated by blank lines) to structured data and scales amounts. images.py validates/re-encodes uploads (WebP 1600 px + 480 px thumb, no EXIF). Photos are served only via recipes:photo (/bilder/<uuid>/<gross|klein>.webp) after a visibility check. Comments: deletable by their author and admins only (CommentQuerySet.deletable_by), rate-limited. tests/test_privacy.py lists every recipe URL – add new recipe routes there.
  • templates/, static/ – global templates, app.css (mobile first, dark mode), app.js.
  • docker/entrypoint.sh – migrate → ensure_admin → purge_ratelimits → gunicorn.
  • scripts/ – init-env.sh (creates .env with secrets), dev_server.py (local run without Docker).

Commands

# Local (Windows dev box: venv lives at C:\Users\kyled\.venvs\kochbuch due to path-length issues)
python -m pytest                                  # tests (SQLite)
python scripts/dev_server.py                      # http://localhost:8000, [email protected]
SECRET_KEY=dev python manage.py makemigrations

# Docker
./scripts/init-env.sh [email protected]           # once
docker compose up -d --build
docker compose --profile test run --rm test       # tests against Postgres
docker compose logs -f app

Conventions

  • Code, comments, commits: English. UI, README, user-facing messages: German (informal "du").
  • Default deny: LoginRequiredMiddleware is active; public views need @login_not_required. Admin views use admin_required (403). Private content that a user may not see → 404, never 403 (don't leak existence).
  • Visibility (Phase 2+): every recipe query for a user goes through one central queryset method (visible_to(user)), including list, detail, search, tags, images, comments. Uploaded files are never served from a static path – only through permission-checked views. Tests must cover each access path.
  • Tags are stored lowercase.
  • One-time tokens (invitations, resets) are stored only as SHA-256 hashes; the link is shown once.
  • Rate limits use core.ratelimit.hit/is_limited; limit 0 = unlimited. AI limits (Phase 4) must exist but default to 0 (owner's request), configurable via env.
  • Forwarded headers are trusted only from TRUSTED_PROXIES; gunicorn does not trust any itself.
  • CSP is strict (script-src 'self', style-src 'self'): no inline scripts/styles/handlers in templates.
  • AI output is always an editable draft, never saved directly. OPENAI_API_KEY only via env.
  • Keep it lean: small server, no heavy dependencies without reason.