458b1e362a
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>
182 lines
7.0 KiB
Python
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})
|