{
  "openapi": "3.1.0",
  "info": {
    "title": "Oberlausitzer Saftpresse – Öffentliche API",
    "description": "Öffentliche Schnittstelle der Oberlausitzer Saftpresse. Ermöglicht das Abrufen veröffentlichter Presstermine und das Einreichen einer Terminanfrage. Keine Authentifizierung erforderlich.",
    "contact": {
      "email": "info@appelsaft.com",
      "url": "https://appelsaft.com"
    },
    "version": "1.0.0",
    "license": {
      "name": "Proprietär"
    }
  },
  "servers": [
    {
      "url": "https://appelsaft.com/api/v1",
      "description": "Produktionsserver"
    }
  ],
  "paths": {
    "/oeffentlich/presstage": {
      "get": {
        "operationId": "getOeffentlichePresstage",
        "summary": "Veröffentlichte Presstermine der aktuellen Saison",
        "description": "Liefert alle Presstermine mit Status `geplant` oder `aktiv`, die für die Öffentlichkeit freigegeben wurden (`veroeffentlicht: true`). Abgeschlossene Termine werden nicht zurückgegeben. Sortierung aufsteigend nach Datum (nächster Termin zuerst).",
        "parameters": [
          {
            "name": "saison",
            "in": "query",
            "description": "Vierstellige Jahreszahl der gewünschten Saison (z. B. `2026`). Ohne Angabe gilt das aktuelle Kalenderjahr des Servers.",
            "required": false,
            "schema": {
              "type": "integer",
              "example": 2026
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Liste der veröffentlichten Presstermine",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OeffentlicherPresstag"
                  }
                },
                "example": [
                  {
                    "datum": "2026-10-03",
                    "subtitel": "Ernte Mustermann & Nachbarn",
                    "ort": "Hof Mustermann, Apfelheim",
                    "max_menge_kg": 500,
                    "summe_kg_angemeldet": 320
                  },
                  {
                    "datum": "2026-10-10",
                    "subtitel": null,
                    "ort": null,
                    "max_menge_kg": null,
                    "summe_kg_angemeldet": 0
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/oeffentlich/anmeldung": {
      "post": {
        "operationId": "postAnmeldung",
        "summary": "Terminanfrage einreichen",
        "description": "Nimmt eine Terminanfrage entgegen und leitet sie per E-Mail an den Betreiber weiter. Eine Bestätigung erfolgt direkt durch den Betreiber. Das Feld `website` ist ein Honeypot-Feld zum Schutz vor automatisierten Bots und muss leer bleiben.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnmeldungRequest"
              },
              "example": {
                "termin": "2026-10-03",
                "name": "Max Mustermann",
                "adresse": {
                  "strasse": "Obstweg 12",
                  "plz": "12345",
                  "ort": "Apfelheim"
                },
                "telefon": "+49 170 1234567",
                "menge_kg": 120,
                "email": "max@example.de",
                "nachricht": "Ich bringe eigene Behälter mit."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Anfrage erfolgreich entgegengenommen und weitergeleitet",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "required": ["ok"]
                },
                "example": { "ok": true }
              }
            }
          },
          "400": {
            "description": "Pflichtfelder fehlen oder Eingaben sind ungültig",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Fehler"
                },
                "examples": {
                  "fehlend": {
                    "summary": "Pflichtfelder fehlen",
                    "value": {
                      "fehler": "Fehlende Pflichtfelder: name, telefon",
                      "code": "FELDER_FEHLEND"
                    }
                  },
                  "menge_zu_klein": {
                    "summary": "Menge unter Minimum",
                    "value": {
                      "fehler": "Mindestmenge beträgt 50 kg",
                      "code": "MENGE_ZU_KLEIN"
                    }
                  },
                  "invalid_termin": {
                    "summary": "Ungültiges Datumsformat",
                    "value": {
                      "fehler": "Ungültiges Datumsformat (YYYY-MM-DD erwartet)",
                      "code": "INVALID_TERMIN"
                    }
                  },
                  "invalid_email": {
                    "summary": "Ungültige E-Mail-Adresse",
                    "value": {
                      "fehler": "Ungültige E-Mail-Adresse",
                      "code": "INVALID_EMAIL"
                    }
                  },
                  "eingabe_zu_lang": {
                    "summary": "Eingabe zu lang",
                    "value": {
                      "fehler": "Eingabe zu lang: name",
                      "code": "EINGABE_ZU_LANG"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "E-Mail-Versand nicht konfiguriert",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Fehler"
                },
                "example": {
                  "fehler": "E-Mail-Versand ist nicht konfiguriert",
                  "code": "EMAIL_NICHT_KONFIGURIERT"
                }
              }
            }
          },
          "502": {
            "description": "SMTP-Übertragungsfehler",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Fehler"
                },
                "example": {
                  "fehler": "E-Mail konnte nicht gesendet werden",
                  "code": "EMAIL_VERSAND_FEHLER"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "OeffentlicherPresstag": {
        "type": "object",
        "description": "Öffentlich sichtbarer Presstermin",
        "properties": {
          "datum": {
            "type": "string",
            "format": "date",
            "description": "Datum des Presstermins (YYYY-MM-DD)",
            "example": "2026-10-03"
          },
          "subtitel": {
            "type": ["string", "null"],
            "description": "Optionaler Untertitel, z. B. Name der Erntgemeinschaft",
            "example": "Ernte Mustermann & Nachbarn"
          },
          "ort": {
            "type": ["string", "null"],
            "description": "Veranstaltungsort",
            "example": "Hof Mustermann, Apfelheim"
          },
          "max_menge_kg": {
            "type": ["integer", "null"],
            "description": "Maximale Annahmemenge in kg. `null` bedeutet keine Begrenzung.",
            "example": 500
          },
          "summe_kg_angemeldet": {
            "type": "integer",
            "description": "Bereits angemeldete Obstmenge in kg. Zusammen mit `max_menge_kg` ergibt sich die verbleibende Kapazität.",
            "example": 320
          }
        },
        "required": ["datum", "subtitel", "ort", "max_menge_kg", "summe_kg_angemeldet"]
      },
      "AnmeldungRequest": {
        "type": "object",
        "description": "Terminanfrage eines Obstanbringers",
        "properties": {
          "termin": {
            "type": "string",
            "format": "date",
            "description": "Gewünschtes Presstermin-Datum (YYYY-MM-DD). Muss einem der unter `/oeffentlich/presstage` gelisteten Termine entsprechen.",
            "example": "2026-10-03"
          },
          "name": {
            "type": "string",
            "maxLength": 200,
            "description": "Vor- und Nachname des Anfragenden",
            "example": "Max Mustermann"
          },
          "adresse": {
            "type": "object",
            "description": "Postanschrift des Anfragenden",
            "properties": {
              "strasse": {
                "type": "string",
                "maxLength": 200,
                "description": "Straße und Hausnummer",
                "example": "Obstweg 12"
              },
              "plz": {
                "type": "string",
                "maxLength": 10,
                "description": "Postleitzahl",
                "example": "12345"
              },
              "ort": {
                "type": "string",
                "maxLength": 100,
                "description": "Ort",
                "example": "Apfelheim"
              }
            },
            "required": ["strasse", "plz", "ort"]
          },
          "telefon": {
            "type": "string",
            "maxLength": 50,
            "description": "Telefonnummer für Rückfragen",
            "example": "+49 170 1234567"
          },
          "menge_kg": {
            "type": "integer",
            "minimum": 50,
            "description": "Geplante Obstmenge in kg (Minimum 50 kg)",
            "example": 120
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254,
            "description": "E-Mail-Adresse (optional). Wird als Reply-To gesetzt.",
            "example": "max@example.de"
          },
          "nachricht": {
            "type": "string",
            "maxLength": 2000,
            "description": "Optionale Anmerkungen, z. B. Behälter-Wünsche",
            "example": "Ich bringe eigene Behälter mit."
          },
        },
        "required": ["termin", "name", "adresse", "telefon", "menge_kg"]
      },
      "Fehler": {
        "type": "object",
        "description": "Fehlermeldung",
        "properties": {
          "fehler": {
            "type": "string",
            "description": "Menschenlesbare Fehlerbeschreibung auf Deutsch"
          },
          "code": {
            "type": "string",
            "description": "Maschinenlesbarer Fehlercode",
            "enum": [
              "FELDER_FEHLEND",
              "INVALID_TERMIN",
              "INVALID_MENGE",
              "MENGE_ZU_KLEIN",
              "EINGABE_ZU_LANG",
              "INVALID_EMAIL",
              "EMAIL_NICHT_KONFIGURIERT",
              "EMAIL_VERSAND_FEHLER"
            ]
          }
        },
        "required": ["fehler", "code"]
      }
    }
  }
}
