7.1 KiB
Architektur und Entwicklung
Stack
Next.js App Router, React, TypeScript, PostgreSQL, Drizzle, Zod, decimal.js, Motion, Lucide, Sharp und @node-rs/argon2. Konkrete kompatible Versionen sind in package-lock.json fixiert. Das UI nutzt bewusst eigenständiges CSS mit Designvariablen statt eines generischen Dashboard-Themes. Lokale DM Sans und Fraunces werden über Fontsource gebündelt.
Verantwortlichkeiten
| Pfad | Verantwortung |
|---|---|
src/lib/recipe.ts |
Gemeinsames Datenmodell, Validierung, Referenzintegrität |
src/lib/calculator.ts |
Reine präzise Skalierung, Einheiten, Anzeige und Einkaufsliste |
src/lib/import.ts |
JSON/XML-Dekodierung und verlustfreier Metadatenexport |
src/lib/cooking.ts |
Testbare Zeitstempel-Timer |
src/lib/server.ts |
Sessions, CSRF, Größenlimits, Rate Limit, Fehlerabbildung |
src/lib/repository.ts |
Rezeptpersistenz, Sichtbarkeit, Versionskonflikte |
src/db/ |
Drizzle-Mappings und PostgreSQL-Pool |
src/app/api/[...path]/route.ts |
Dünne HTTP-Operationen mit Validierung/Autorisierung |
src/components/ |
Bibliothek, Detail, Kochmodus, Editor, Administration |
migrations/ |
Versionierte SQL-Migrationen |
scripts/ |
Migration, Admin-Bootstrap, Schema/Beispielgenerierung |
Die Rezeptstruktur liegt als validiertes JSONB-Dokument vor, ergänzt um relationale ID, Titel, normalisierten eindeutigen Titel, Veröffentlichungsstatus und Zeitstempel. Damit werden Zutaten/Schritte atomar geändert und beim Import nicht auseinandergerissen. Sessions, Administratoren, Limits und Medien sind getrennte relationale Tabellen. Öffentliche Leseoperationen filtern serverseitig auf published=true.
API
Alle Fehler: { "error": "Verständliche Meldung" }. Statuscodes: 400 Syntax, 401 Anmeldung, 403 Origin, 404 fehlt, 409 Duplikat/Versionskonflikt, 413 Größe, 415 Dateityp, 422 Datenvalidierung, 428 fehlende Bearbeitungsversion, 429 Loginlimit, 500 unerwarteter Serverfehler. Unerwartete Fehler werden serverseitig geloggt, ohne interne Details an Clients auszugeben.
GET /api/recipes: veröffentlichte Rezepte;?admin=1nur mit gültiger Session erweitert.GET /api/recipes/:id: öffentlich nur veröffentlicht, sonst autorisierter Admin.POST /api/recipes,PUT /api/recipes/:id,DELETE /api/recipes/:id: Admin, Origin erforderlich. PUT benötigt den letztenupdatedAt-Wert.POST /api/auth/login,POST /api/auth/logout,GET /api/auth/me,POST /api/auth/password.POST /api/import:{raw, format: "json"|"xml", preview: boolean}.GET /api/export?format=json|xml&id=UUID: ID optional, sonst gesamte Sammlung.GET/PUT /api/settings: Kategorien-/Tag-Auswahllisten und Sammlungsname.GET/POST/DELETE /api/media: Rohbild-Upload mit Content-Type JPEG/PNG/WebP, Löschung per{name}. Bilder werden serverseitig dekodiert und als WebP neu kodiert (max. 40 MP, 10 MB Eingabe, 2400 px Kantenlänge). EXIF wird entfernt.GET /api/health: Datenbankverbindung + Anwendung.
Alle Admin-Operationen prüfen die Session auf dem Server. Passwörter werden mit Argon2id (64 MiB, 3 Iterationen) gehasht. CSRF-Schutz nutzt den verpflichtenden exakten Origin-Vergleich plus SameSite-Strict-Cookies. Automatisierte API-Clients müssen die konfigurierte Origin explizit setzen. Es gibt keine Token in Local Storage.
Rechner und Referenzen
Faktoren bleiben als hochpräzise Dezimalstrings erhalten. Neue Zielmengen werden stets durch die unveränderte Originalmenge geteilt; Originalzutaten werden nie mutiert. Runden erfolgt nur zur Anzeige. Übliche Viertel-/Halbbrüche werden typografisch dargestellt, kleine Mengen mit bis zu sechs Nachkommastellen. Nährwerte sind pro Originalportion, Gesamtzeit wird nicht skaliert.
Editor und lokale Speicherung
Änderungen werden lokal im Browser gesichert, ohne das veröffentlichte Rezept zu verändern. „Rezept speichern“ validiert und überträgt ausdrücklich auf den Server. Der Browser warnt beim Verlassen mit noch nicht auf den Server übertragenen Änderungen. Konflikte mit einer zwischenzeitlich geänderten Fassung werden als 409 zurückgegeben. Lokale Entwürfe bleiben dann erhalten.
Zutaten werden als kompakte Liste mit Name, Menge und Einheit erfasst. Abschnitt, Hinweis und die optionale Kennzeichnung lassen sich pro Zeile aufklappen; ausgefüllte Details bleiben als kurze Zusammenfassung sichtbar. Enter im Zutatenname fügt die nächste Zeile hinzu und fokussiert sie direkt. Eine bereits leere Zeile wird wiederverwendet. Neue Zutaten übernehmen den letzten Abschnitt.
Zutaten und Schritte lassen sich per Drag-Handle sowie per Auf/Ab-Buttons sortieren; bei Zutaten befinden sich die Buttons in den aufgeklappten Details. So ist die Funktion auch ohne Maus bedienbar. Bilder werden sofort in die lokale Medienablage hochgeladen und können dort später entfernt werden, solange kein Rezept sie verwendet.
Titelbild, Rezeptgalerie und Schrittbilder unterstützen auch direkte HTTPS-Bildadressen. Diese werden ausschließlich im Browser geladen, nicht serverseitig abgerufen. imageSchema in src/lib/recipe.ts validiert lokale Pfade und HTTPS-URLs; die CSP erlaubt HTTPS-Bilder. RecipeImage setzt no-referrer und zeigt bei Ladefehlern einen Platzhalter. Die Medienlöschung berücksichtigt auch Bildreferenzen innerhalb von Schritten.
steps[].images ist optional beim Import (Standard [], maximal 10 Bilder). Bestehende Datenbankeinträge und lokale Entwürfe erhalten beim Lesen leere Schrittbilder; eine Datenbankmigration ist dafür nicht erforderlich. JSON- und XML-Export erhalten die Referenzen. Externe Bilder werden nicht durch den Service Worker offline gespeichert.
StepInstructions wird in Rezeptansicht, Kochmodus und Vorschauen gemeinsam verwendet: kurze Absätze, einfache Listen und hervorgehobene skalierbare Zutatenreferenzen. instructionBlocks zerlegt ausschließlich Klartext; lange ältere Ein-Absatz-Texte werden mit Intl.Segmenter für Deutsch nach jeweils zwei Sätzen optisch gegliedert. Es wird kein HTML interpretiert und der gespeicherte Anleitungstext wird nicht umgeschrieben.
Tests
Unit: npm test. E2E: npm run build && npm run test:e2e. Tests verwenden echte PostgreSQL-Persistenz, echte HTTP-Authentifizierung und Chromium ohne gemockte Produktfunktionen. Die E2E-Datenbank muss auf _test enden und wird zurückgesetzt. Playwright erzeugt Screenshots für Desktop (1440 px), Smartphone (390 px) und Tablet (768 px).
Bei Schemaänderungen npm run schema:generate ausführen und generierte Dokumente mitcommitten. Entwicklungsserver und Produktionsbuild teilen dieselbe Datenbankkonfiguration. Next-Telemetrie im Container ist deaktiviert; lokal bei Bedarf NEXT_TELEMETRY_DISABLED=1 setzen.