Files
recipe-importer/app/main.py
T
admin 458b1e362a 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>
2026-02-24 09:29:58 +01:00

182 lines
7.0 KiB
Python

"""Flask application — recipe importer web UI."""
import os
import traceback
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify
from app import config
from app.scraper import scrape
from app.mealie import MealieClient
from app.tandoor import TandoorClient
app = Flask(
__name__,
template_folder=os.path.join(os.path.dirname(__file__), "templates"),
static_folder=os.path.join(os.path.dirname(__file__), "static"),
)
app.secret_key = os.environ.get("SECRET_KEY", "recipe-importer-dev-key")
VERSION = os.environ.get("VERSION", "dev")
# ---------------------------------------------------------------------------
# Routes
# ---------------------------------------------------------------------------
@app.route("/")
def index():
"""Redirect to the import page (or settings if not configured)."""
cfg = config.load()
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"))
@app.route("/settings", methods=["GET", "POST"])
def settings():
"""Configure Mealie connection."""
cfg = config.load()
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"))
return render_template("settings.html", cfg=cfg, version=VERSION)
@app.route("/settings/test", methods=["POST"])
def settings_test():
"""AJAX endpoint — test Mealie connection using form values."""
url = (request.form.get("mealie_url") or "").strip().rstrip("/")
key = (request.form.get("mealie_api_key") or "").strip()
if not url or not key:
return jsonify({"ok": False, "error": "Nincs megadva Mealie URL vagy API kulcs."})
try:
client = MealieClient(url, key, api_url=config.MEALIE_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("/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()
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,
has_mealie=has_mealie, has_tandoor=has_tandoor)
@app.route("/scrape", methods=["POST"])
def scrape_url():
"""AJAX — scrape a recipe URL and return structured data."""
url = request.form.get("url", "").strip()
if not url:
return jsonify({"ok": False, "error": "Nincs URL megadva."})
try:
data = scrape(url)
# 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:
client = MealieClient(cfg["mealie_url"], cfg["mealie_api_key"],
api_url=config.MEALIE_INTERNAL_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,
"tandoor_duplicate": tandoor_duplicate})
except Exception as exc:
return jsonify({"ok": False, "error": str(exc), "trace": traceback.format_exc()})
@app.route("/send", methods=["POST"])
def send_to_mealie():
"""AJAX — send edited recipe data to Mealie."""
cfg = config.load()
if not cfg.get("mealie_url") or not cfg.get("mealie_api_key"):
return jsonify({"ok": False, "error": "Mealie 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 = MealieClient(cfg["mealie_url"], cfg["mealie_api_key"],
api_url=config.MEALIE_INTERNAL_URL)
slug = client.create_recipe(payload)
recipe_url = f"{cfg['mealie_url']}/g/home/r/{slug}"
return jsonify({"ok": True, "slug": slug, "url": recipe_url})
except Exception as exc:
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
# ---------------------------------------------------------------------------
@app.route("/health")
def health():
return jsonify({"status": "ok", "version": VERSION})