84 lines
5.2 KiB
Markdown
84 lines
5.2 KiB
Markdown
# Importformat HamHam v1
|
||
|
||
## Ablauf
|
||
|
||
Administration → Importieren → JSON/XML auswählen → Datei hochladen oder Text einfügen → Vorschau prüfen → Import bestätigen. Maximal 2 MB und 100 Rezepte pro Vorgang. Kein Datensatz wird gespeichert, solange die Vorschau läuft. Ein Fehler oder Duplikat blockiert die gesamte Übernahme.
|
||
|
||
Titel werden für die Duplikatprüfung mit NFKC normalisiert, getrimmt und ohne Groß-/Kleinschreibung verglichen. Vorhandene IDs gelten ebenfalls als Duplikat. Import überschreibt keine bestehenden Rezepte. Zum Bearbeiten den Editor verwenden; zum Anlegen einer Variante eine neue ID bzw. keine ID und einen eigenen Titel verwenden.
|
||
|
||
JSON-Dateien haben diese Hülle:
|
||
|
||
```json
|
||
{
|
||
"version": 1,
|
||
"recipes": [{ "title": "Mein Rezept", "servings": 2, "ingredients": [], "steps": [] }]
|
||
}
|
||
```
|
||
|
||
Die leeren Arrays im obigen Strukturdiagramm müssen mit mindestens einer gültigen Zutat und einem Schritt befüllt werden. Ein vollständig gültiges Beispiel steht in `public/examples/pljeskavica.json`. Das verbindliche maschinenlesbare Schema liegt in `public/examples/hamham.schema.json` (JSON Schema 2020-12).
|
||
|
||
## Felder
|
||
|
||
Pflicht: `title` (2–180 Zeichen), `servings` (positiv), `ingredients` (1–200), `steps` (1–100).
|
||
|
||
Optional: `id` (UUID), `subtitle`, `description`, `image`, `images`, `categories`, `tags`, `cuisine`, `difficulty` (`Einfach`, `Mittel`, `Anspruchsvoll`), `prepMinutes`, `cookMinutes`, `restMinutes`, `tips`, `storage`, `nutrition`, `published`, `createdAt`, `updatedAt` (UTC ISO-8601). Gesamtzeit wird aus den drei Zeitfeldern berechnet. `published` ist standardmäßig `false`. Nährwerte gelten pro Originalportion; `nutrition` ist `null` oder `{ "kcal": 500, "protein": 25, "carbs": 60, "fat": 18 }`.
|
||
|
||
Zutat:
|
||
|
||
```json
|
||
{
|
||
"id": "huhn",
|
||
"name": "Hühnerbrust",
|
||
"amount": 400,
|
||
"unit": "g",
|
||
"section": "Hauptgericht",
|
||
"note": "ohne Haut",
|
||
"optional": false
|
||
}
|
||
```
|
||
|
||
`amount`: positive JSON-Zahl oder `null`. Dezimalpunkt verwenden, keine Zeichenketten wie `"1/2"`, `"400 g"` oder `"1,5"`. Für eine halbe Zitrone `0.5` und `Stück` verwenden. Bei Salz nach Geschmack `amount: null`, `unit: ""`, `note: "nach Geschmack"`.
|
||
|
||
Einheiten: `g`, `kg`, `ml`, `l`, `Stück`, `TL`, `EL`, `Prise`, `Bund`, `""`. Umrechnung: 1 kg = 1000 g, 1 l = 1000 ml, 1 TL = 5 ml, 1 EL = 15 ml. Masse wird nie ohne Dichteannahme in Volumen umgerechnet. IDs sind innerhalb des Rezepts eindeutig, bestehen aus Buchstaben, Ziffern, `_` oder `-`, maximal 80 Zeichen.
|
||
|
||
Schritt:
|
||
|
||
```json
|
||
{
|
||
"id": "anbraten",
|
||
"title": "Goldbraun anbraten",
|
||
"text": "{{huhn}} in Würfel schneiden und anbraten.",
|
||
"ingredients": ["huhn"],
|
||
"timers": [{ "id": "bratzeit", "label": "Hühnerbrust braten", "seconds": 480 }]
|
||
}
|
||
```
|
||
|
||
`{{huhn}}` wird zu **Menge + Einheit + Zutatenname + Hinweis** aufgelöst. Diese Referenz bleibt beim Skalieren konsistent. Keine konkreten skalierbaren Mengen als Freitext wiederholen. Temperaturen und Zeiten bleiben normaler Text. `ingredients` enthält die in diesem Schritt benötigten Zutaten-IDs. Timer-IDs müssen im gesamten Rezept eindeutig sein; ein Timer dauert 1–172800 Sekunden. Zeiten werden nicht mit Portionen skaliert.
|
||
|
||
## Bilder
|
||
|
||
Nur lokale URLs: `/media/<uuid>.webp` oder mitgelieferte `/images/<name>.jpg|webp|svg`. Bilder zuerst im Adminbereich hochladen, dann den lokalen Pfad verwenden. ChatGPT soll `image: ""` und `images: []` setzen und keine erfundenen URLs ausgeben.
|
||
|
||
JSON/XML-Exporte enthalten Bildreferenzen, keine Binärdaten. Für einen Umzug das Bildvolume mitnehmen; für ein vollständiges Backup zusätzlich die Datenbank sichern. Metadaten, Zutaten, Schritte, Timer, Zeitstempel und IDs bleiben beim Export erhalten.
|
||
|
||
## XML
|
||
|
||
Das vollständige Beispiel ist `public/examples/pljeskavica.xml`. UTF-8, Wurzel `<hamham version="1">`, darunter `<recipes><recipe>…</recipe></recipes>`. Rezeptfelder entsprechen JSON. Array-Container:
|
||
|
||
| JSON | XML |
|
||
| ---------------- | ----------------------------------------------------------- |
|
||
| recipes | `<recipes><recipe>…</recipe></recipes>` |
|
||
| ingredients | `<ingredients><ingredient>…</ingredient></ingredients>` |
|
||
| steps | `<steps><step>…</step></steps>` |
|
||
| timers | `<timers><timer>…</timer></timers>` |
|
||
| categories | `<categories><category>Vegetarisch</category></categories>` |
|
||
| tags | `<tags><tag>Schnell</tag></tags>` |
|
||
| images | `<images><image>/media/…webp</image></images>` |
|
||
| step.ingredients | `<ingredients><ref>huhn</ref></ingredients>` |
|
||
|
||
Leere Menge: `<amount/>`. Keine Nährwerte: `<nutrition/>`. Boolean: `true`/`false`. Texte standardkonform escapen (`&`, `<` etc.). DOCTYPE und Entity-Deklarationen sind verboten, es werden keine externen Ressourcen aufgelöst. Nach dem XML-Parsing gilt dieselbe Zod-Validierung wie bei JSON.
|
||
|
||
## Versionsstrategie
|
||
|
||
Die Hülle `version: 1` ist verbindlich. Inkompatible Änderungen bekommen eine neue Version mit expliziter Konvertierung. Unbekannte Versionen werden abgewiesen. Optionale Felder erhalten dokumentierte Standardwerte. HTML-Inhalte werden nicht übernommen.
|