v0.6.0: Sobors.hu parser, HTTP auth, recipe validation, UI polish
- New sobors.hu parser with ingredient groups and section headers - Incomplete recipe warnings (missing ingredients/instructions) - Optional HTTP Basic Auth (configurable on settings page) - Brand text: "Recept" in white, "Importáló" in blue - Larger logo (36px), favicon using logo_notext.svg Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+63
-2
@@ -1,9 +1,11 @@
|
||||
"""Flask application — recipe importer web UI."""
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import secrets
|
||||
import traceback
|
||||
|
||||
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, send_from_directory
|
||||
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, send_from_directory, Response
|
||||
|
||||
from app import config
|
||||
from app.scraper import scrape, supported_sites
|
||||
@@ -20,6 +22,42 @@ app.secret_key = os.environ.get("SECRET_KEY", "recipe-importer-dev-key")
|
||||
VERSION = os.environ.get("VERSION", "dev")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Basic auth middleware
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_AUTH_EXEMPT = {"/health", "/assets/logo.svg", "/assets/logo_notext.svg"}
|
||||
|
||||
|
||||
@app.before_request
|
||||
def _check_basic_auth():
|
||||
"""Enforce HTTP Basic Auth if configured."""
|
||||
if request.path in _AUTH_EXEMPT or request.path.startswith("/assets/"):
|
||||
return None
|
||||
cfg = config.load()
|
||||
auth_user = cfg.get("auth_username", "").strip()
|
||||
auth_hash = cfg.get("auth_password_hash", "").strip()
|
||||
if not auth_user or not auth_hash:
|
||||
return None # auth not configured — allow all
|
||||
|
||||
auth = request.authorization
|
||||
if (auth
|
||||
and auth.type == "basic"
|
||||
and auth.username == auth_user
|
||||
and _hash_password(auth.password) == auth_hash):
|
||||
return None # credentials match
|
||||
|
||||
return Response(
|
||||
"Hitelesítés szükséges.", 401,
|
||||
{"WWW-Authenticate": 'Basic realm="Recept Importáló"'},
|
||||
)
|
||||
|
||||
|
||||
def _hash_password(password: str) -> str:
|
||||
"""SHA-256 hex digest of password."""
|
||||
return hashlib.sha256(password.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Routes
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -46,6 +84,20 @@ def settings():
|
||||
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()
|
||||
|
||||
# Auth settings
|
||||
new_user = request.form.get("auth_username", "").strip()
|
||||
new_pass = request.form.get("auth_password", "").strip()
|
||||
if new_user:
|
||||
cfg["auth_username"] = new_user
|
||||
if new_pass:
|
||||
# Only update password hash if a new password was provided
|
||||
cfg["auth_password_hash"] = _hash_password(new_pass)
|
||||
else:
|
||||
# Clear auth if username removed
|
||||
cfg.pop("auth_username", None)
|
||||
cfg.pop("auth_password_hash", None)
|
||||
|
||||
config.save(cfg)
|
||||
flash("Beállítások mentve.", "success")
|
||||
return redirect(url_for("settings"))
|
||||
@@ -125,8 +177,17 @@ def scrape_url():
|
||||
except Exception:
|
||||
pass # non-fatal
|
||||
|
||||
# Validate completeness
|
||||
warnings = []
|
||||
real_ingredients = [i for i in data.get("ingredients", []) if "group" not in i]
|
||||
if not real_ingredients:
|
||||
warnings.append("A recept nem tartalmaz hozzávalókat.")
|
||||
if not data.get("instructions"):
|
||||
warnings.append("A recept nem tartalmaz elkészítési lépéseket.")
|
||||
|
||||
return jsonify({"ok": True, "data": data, "duplicate": duplicate,
|
||||
"tandoor_duplicate": tandoor_duplicate})
|
||||
"tandoor_duplicate": tandoor_duplicate,
|
||||
"warnings": warnings})
|
||||
except Exception as exc:
|
||||
return jsonify({"ok": False, "error": str(exc), "trace": traceback.format_exc()})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user