"""Mealie einrichten und prüfen. Die Zugangsdaten nimmt dieser Router entgegen, gibt sie aber nie zurück. Die Oberfläche erfährt nur „eingerichtet ja/nein" — dasselbe Muster wie bei ``GET /auth/oidc/providers``, wo Client-Secrets ebenfalls nie in der Antwort stehen. Bewusst eigene Routen mit JSON-Rumpf statt ``PUT /settings/{key}?value=``: Query-Parameter landen in Zugriffsprotokollen, und ein Passwort hat dort nichts zu suchen. """ # ACHTUNG: Kein ``from __future__ import annotations`` – siehe package_types.py. from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from ..database import get_db from ..deps import get_current_user, require_admin from ..models import Setting, User from ..schemas import ( MealieAvailable, MealieConfigIn, MealieStatus, MealieTokenIn, ) from ..services import mealie router = APIRouter(prefix="/mealie", tags=["mealie"]) def _setze_adresse(db: Session, base_url: str) -> str: sauber = base_url.strip().rstrip("/") row = db.get(Setting, mealie.BASE_URL_KEY) if row is None: db.add(Setting(key=mealie.BASE_URL_KEY, value=sauber)) else: row.value = sauber db.commit() return sauber def _status(db: Session, pruefen: bool) -> MealieStatus: adresse = mealie.basis_adresse(db) hat_token = bool(mealie.token(db)) status_out = MealieStatus( configured=bool(adresse) and hat_token, base_url=adresse, token_set=hat_token, ) if not pruefen or not status_out.configured: return status_out try: status_out.version = mealie.version(db) # Eine echte Abfrage – „erreichbar" soll heissen, dass auch das Token gilt. mealie.rezepte(db, pro_seite=1) status_out.reachable = True except mealie.MealieFehler as fehler: status_out.reachable = False status_out.error = str(fehler) return status_out @router.get("/status", response_model=MealieStatus) def mealie_status( pruefen: bool = False, db: Session = Depends(get_db), _: User = Depends(require_admin), ) -> MealieStatus: """Was eingerichtet ist – ohne das Token. ``pruefen=true`` fragt Mealie an.""" return _status(db, pruefen) @router.get("/available", response_model=MealieAvailable) def mealie_available( db: Session = Depends(get_db), _: User = Depends(get_current_user), ) -> MealieAvailable: """Ob die Rezept-Seite etwas anzeigen kann. Für alle angemeldeten Benutzer, damit die Oberfläche den Menüpunkt nur dann zeigt. Anders als bei den Anmeldeanbietern **mit** Anmeldung: Es gibt keinen Grund, Unbekannten zu verraten, dass hier eine Mealie-Instanz hängt. """ return MealieAvailable(available=mealie.eingerichtet(db)) @router.put("/config", response_model=MealieStatus) def mealie_konfigurieren( payload: MealieConfigIn, db: Session = Depends(get_db), _: User = Depends(require_admin), ) -> MealieStatus: """Mit Benutzername und Passwort anmelden und ein Dauertoken holen. Das Passwort wird nur für diese eine Anmeldung benutzt und danach verworfen – es steht in keiner Zeile der Datenbank und in keiner Antwort. """ try: dauertoken = mealie.hole_dauertoken( payload.base_url, payload.username, payload.password ) except mealie.MealieFehler as fehler: raise HTTPException(_http_code(fehler), str(fehler)) from fehler _setze_adresse(db, payload.base_url) mealie.speichere_token(db, dauertoken) return _status(db, pruefen=True) @router.put("/token", response_model=MealieStatus) def mealie_token_setzen( payload: MealieTokenIn, db: Session = Depends(get_db), _: User = Depends(require_admin), ) -> MealieStatus: """Ein in Mealie selbst erzeugtes Dauertoken einsetzen. Für alle, die ihr Passwort nicht eintippen wollen: In Mealie unter Profil ▸ API-Tokens eines anlegen und hier einfügen. """ _setze_adresse(db, payload.base_url) mealie.speichere_token(db, payload.token.strip()) return _status(db, pruefen=True) @router.delete("/config", status_code=status.HTTP_204_NO_CONTENT) def mealie_trennen( db: Session = Depends(get_db), _: User = Depends(require_admin), ) -> None: """Verbindung lösen: Adresse und Token hier entfernen. Das Token selbst bleibt in Mealie bestehen – wer es dort endgültig los sein will, löscht es unter Profil ▸ API-Tokens. """ mealie.loesche_zugang(db) def _http_code(fehler: mealie.MealieFehler) -> int: """Fehlerkennung auf einen Statuscode abbilden. Getrennt, weil „Mealie ist weg" (502) etwas anderes ist als „dein Passwort stimmt nicht" (400) – und die Oberfläche beides verschieden erklären soll. """ if fehler.kennung in ("anmeldung_fehlgeschlagen",): return status.HTTP_400_BAD_REQUEST if fehler.kennung == "nicht_eingerichtet": return status.HTTP_503_SERVICE_UNAVAILABLE return status.HTTP_502_BAD_GATEWAY