feat: Tandoor integration — settings, test connection, import, duplicate detection

Add TandoorClient (app/tandoor.py) with full recipe creation, image upload,
and duplicate detection via the Tandoor REST API. Settings page now has
separate Mealie and Tandoor sections. Import page shows both send buttons
based on which services are configured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 09:29:58 +01:00
parent f7810ba33d
commit 458b1e362a
6 changed files with 476 additions and 68 deletions
+58 -6
View File
@@ -8,6 +8,7 @@ from flask import Flask, render_template, request, redirect, url_for, flash, jso
from app import config
from app.scraper import scrape
from app.mealie import MealieClient
from app.tandoor import TandoorClient
app = Flask(
__name__,
@@ -28,7 +29,9 @@ VERSION = os.environ.get("VERSION", "dev")
def index():
"""Redirect to the import page (or settings if not configured)."""
cfg = config.load()
if not cfg.get("mealie_url") or not cfg.get("mealie_api_key"):
has_mealie = bool(cfg.get("mealie_url") and cfg.get("mealie_api_key"))
has_tandoor = bool(cfg.get("tandoor_url") and cfg.get("tandoor_api_key"))
if not has_mealie and not has_tandoor:
return redirect(url_for("settings"))
return redirect(url_for("import_page"))
@@ -41,6 +44,8 @@ def settings():
if request.method == "POST":
cfg["mealie_url"] = request.form.get("mealie_url", "").strip().rstrip("/")
cfg["mealie_api_key"] = request.form.get("mealie_api_key", "").strip()
cfg["tandoor_url"] = request.form.get("tandoor_url", "").strip().rstrip("/")
cfg["tandoor_api_key"] = request.form.get("tandoor_api_key", "").strip()
config.save(cfg)
flash("Beállítások mentve.", "success")
return redirect(url_for("settings"))
@@ -63,14 +68,32 @@ def settings_test():
return jsonify({"ok": False, "error": str(exc)})
@app.route("/settings/test-tandoor", methods=["POST"])
def settings_test_tandoor():
"""AJAX endpoint — test Tandoor connection using form values."""
url = (request.form.get("tandoor_url") or "").strip().rstrip("/")
key = (request.form.get("tandoor_api_key") or "").strip()
if not url or not key:
return jsonify({"ok": False, "error": "Nincs megadva Tandoor URL vagy API kulcs."})
try:
client = TandoorClient(url, key, api_url=config.TANDOOR_INTERNAL_URL)
info = client.test_connection()
return jsonify({"ok": True, "data": info})
except Exception as exc:
return jsonify({"ok": False, "error": str(exc)})
@app.route("/import", methods=["GET"])
def import_page():
"""Show the import form."""
cfg = config.load()
if not cfg.get("mealie_url") or not cfg.get("mealie_api_key"):
flash("Először állítsd be a Mealie kapcsolatot.", "warning")
has_mealie = bool(cfg.get("mealie_url") and cfg.get("mealie_api_key"))
has_tandoor = bool(cfg.get("tandoor_url") and cfg.get("tandoor_api_key"))
if not has_mealie and not has_tandoor:
flash("Először állíts be legalább egy szolgáltatást (Mealie vagy Tandoor).", "warning")
return redirect(url_for("settings"))
return render_template("import.html", cfg=cfg, version=VERSION)
return render_template("import.html", cfg=cfg, version=VERSION,
has_mealie=has_mealie, has_tandoor=has_tandoor)
@app.route("/scrape", methods=["POST"])
@@ -82,8 +105,9 @@ def scrape_url():
try:
data = scrape(url)
# Check for duplicate in Mealie
# Check for duplicates in Mealie and Tandoor
duplicate = None
tandoor_duplicate = None
cfg = config.load()
if cfg.get("mealie_url") and cfg.get("mealie_api_key"):
try:
@@ -92,8 +116,16 @@ def scrape_url():
duplicate = client.find_duplicate(url, data.get("title", ""))
except Exception:
pass # non-fatal
if cfg.get("tandoor_url") and cfg.get("tandoor_api_key"):
try:
client = TandoorClient(cfg["tandoor_url"], cfg["tandoor_api_key"],
api_url=config.TANDOOR_INTERNAL_URL)
tandoor_duplicate = client.find_duplicate(url, data.get("title", ""))
except Exception:
pass # non-fatal
return jsonify({"ok": True, "data": data, "duplicate": duplicate})
return jsonify({"ok": True, "data": data, "duplicate": duplicate,
"tandoor_duplicate": tandoor_duplicate})
except Exception as exc:
return jsonify({"ok": False, "error": str(exc), "trace": traceback.format_exc()})
@@ -119,6 +151,26 @@ def send_to_mealie():
return jsonify({"ok": False, "error": str(exc), "trace": traceback.format_exc()})
@app.route("/send-tandoor", methods=["POST"])
def send_to_tandoor():
"""AJAX — send edited recipe data to Tandoor."""
cfg = config.load()
if not cfg.get("tandoor_url") or not cfg.get("tandoor_api_key"):
return jsonify({"ok": False, "error": "Tandoor nincs beállítva."})
payload = request.get_json(silent=True)
if not payload:
return jsonify({"ok": False, "error": "Érvénytelen kérés."})
try:
client = TandoorClient(cfg["tandoor_url"], cfg["tandoor_api_key"],
api_url=config.TANDOOR_INTERNAL_URL)
result = client.create_recipe(payload)
return jsonify({"ok": True, "id": result["id"], "url": result["url"]})
except Exception as exc:
return jsonify({"ok": False, "error": str(exc), "trace": traceback.format_exc()})
# ---------------------------------------------------------------------------
# Health
# ---------------------------------------------------------------------------