- Django 5.2 + PostgreSQL 17 via docker compose (app + db, volumes, healthcheck on /healthz, test profile running pytest against Postgres) - Custom e-mail user model with user/admin roles - Invitation-only registration with single-use, hashed, expiring links - Admin area: invitations, role changes, deactivation, password reset via one-time link or temporary password (forced change on login) - First admin created from ADMIN_EMAIL/ADMIN_PASSWORD on startup - Login rate limiting per e-mail and IP (DB-backed, worker-safe) - Proxy middleware honouring X-Forwarded-* only from TRUSTED_PROXIES, optional CLIENT_IP_HEADER (e.g. CF-Connecting-IP) - Security headers (CSP, HSTS, Permissions-Policy), secure __Host- cookies - Mobile-first German UI, CLAUDE.md, README, init-env script - 65 tests for auth, permissions, proxy handling and rate limiting Co-Authored-By: Claude Opus 5.5 <[email protected]>
3.6 KiB
3.6 KiB
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, 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, HTMX planned for Phase 2+, PostgreSQL 17, gunicorn (gthread), WhiteNoise for static files. No Redis, no JS build step.
Layout
config/– settings (all env-driven, see.env.example), urls, wsgi.settings_test.pyfor 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/– customUser(e-mail login,role= user|admin),Invitation,PasswordResetToken, login/registration/reset views, admin area (admin_views.py,/verwaltung/…),ensure_adminmanagement command (first admin from env).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:
LoginRequiredMiddlewareis active; public views need@login_not_required. Admin views useadmin_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_KEYonly via env. - Keep it lean: small server, no heavy dependencies without reason.