docker compose passes an empty .env line as OPENAI_BASE_URL="", and with
base_url=None the openai SDK fell back to that empty value, so every
call failed with "Connection error". Always pass the base URL
explicitly. Also disable gunicorn's unused control socket, which
logged a permission error because /app is read-only for the app user.
Found while deploying to the homelab server.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Real calls produced "2 Stück Eier" and "1 Zehen Knoblauchzehe". The
prompt now asks for no unit on countable items and no unit repeated in
the name; verified with a real gpt-5-mini call.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A real API call returned 429 "You have no credits remaining" without the
word "quota", so the UI wrongly said the AI was busy. Match quota,
credit and billing wording; test uses the real message.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Optional OPENAI_BASE_URL for OpenAI-compatible endpoints or proxies
- Tests run the real openai SDK against a local stub server: request
shape (model, auth, strict json_schema), text cleanup, photo import
and an authentication error mapped to the UI message
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Runs backup.sh/restore.sh against a logging docker stub and checks
archive content, retention, data flow, .env recovery and the order of
stopping/starting the app.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Lint rules (pycodestyle, pyflakes, bugbear, isort, pyupgrade, bandit)
in pyproject.toml; ruff pinned in requirements-dev
- Full suite verified against PostgreSQL (embedded pgserver), plus a
production-like check: collectstatic with manifest storage, fresh
migrations, ensure_admin and all main pages with DEBUG off
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- backup.sh: pg_dump (custom format), media volume and .env in one
archive with retention (KEEP), cron example
- restore.sh: confirmation, restores .env if missing, pg_restore
--clean in one transaction, replaces media, restarts the app
- Fix: MEDIA_ROOT=/data/media in the image so photos land on the volume
- README: installation, Nginx/Cloudflare client-IP setup, configuration
table, AI and Instagram notes, update, backup, troubleshooting, security
- Test settings independent of the local .env
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- SSRF-safe fetcher: public IPs only, connects to the checked address,
redirects re-validated, size/time limits, honest user agent
- schema.org/Recipe JSON-LD extraction (graphs, HowToSections, ISO
durations, yields, keywords, images) without extra dependencies;
AI fallback on plain page text when no structured recipe exists
- Instagram: one login-free request to the public embed page; when
Instagram withholds the caption, the import page explains how to paste
the text or import a screenshot instead
- Suggested source photo can be downloaded and attached on save
- Text import works without AI (text becomes the description)
- Installable PWA with manifest, icons and Android share target
- Offline tests for SSRF rules, parsers, Instagram and import views
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- recipes/ai.py: Chat Completions with strict JSON schema (Structured
Outputs), Pydantic re-validation, plausibility clamping, prompt treats
input as data only
- "Mit KI aufbereiten" on create/edit form returns an editable draft
- Import page: photo/screenshot (vision, downscaled JPEG, not stored) and
pasted text; drafts pre-fill the recipe form with a preview
- Clear UI messages for timeout, rate limit, quota, auth, model and
connection errors; without OPENAI_API_KEY AI features show a notice
- Optional per-user and global daily AI limits (default unlimited)
- Tests for response parsing, schema strictness, error mapping, views
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- 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]>
- Recipe model with ingredients (amount/range, unit, group, note), steps,
lowercase tags, author notes, source URL and private/public visibility
- Central RecipeQuerySet.visible_to() used by every read path; edit only
by owner, delete by owner or admin (visible recipes only)
- Text-based editing: ingredient/step parser with roundtrip and scaling
- Photo upload (JPEG/PNG/WebP/HEIC), re-encoded to WebP without EXIF,
thumbnails, cover image, served only through permission-checked view
- Search across title, description, ingredients, tags; ingredient and tag
filters; "mine" and favourites views
- Admin can delete all recipes of a deactivated user
- Acceptance tests: private recipes unreachable for others and admins
in list, search, tags, favourites, detail, edit, delete and image URLs
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- 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]>