"""Maker blueprint - recipe creation and editing.""" from flask import Blueprint, flash, jsonify, redirect, render_template, request, url_for from flask_babel import gettext as _ from blueprints.auth import login_required, role_required from services.api_client import api_client maker_bp = Blueprint("maker", __name__, url_prefix="/maker") # ============================================================================ # PAGINE (GET) # ============================================================================ @maker_bp.route("/recipes") @login_required @role_required("Maker") def recipe_list(): """List all recipes with filters.""" page = request.args.get("page", 1, type=int) per_page = request.args.get("per_page", 100, type=int) search = request.args.get("search", "", type=str) params = {"page": page, "per_page": per_page} if search: params["search"] = search resp = api_client.get("/api/recipes", params=params) if resp.get("error"): flash(_("Errore nel caricamento delle ricette: %(error)s", error=resp.get("detail", "")), "error") recipes = [] total = 0 pages = 0 else: recipes = resp.get("items", []) total = resp.get("total", 0) pages = resp.get("pages", 1) return render_template( "maker/recipe_list.html", recipes=recipes, page=page, per_page=per_page, total=total, pages=pages, search=search ) @maker_bp.route("/recipes/new") @login_required @role_required("Maker") def recipe_new(): """Create new recipe.""" return render_template("maker/recipe_editor.html", recipe=None) @maker_bp.route("/recipes//edit") @login_required @role_required("Maker") def recipe_edit(recipe_id: int): """Edit existing recipe.""" # Carica recipe con dettagli completi resp = api_client.get(f"/api/recipes/{recipe_id}") if resp.get("error"): flash(_("Errore nel caricamento della ricetta: %(error)s", error=resp.get("detail", "")), "error") return redirect(url_for("maker.recipe_list")) recipe = resp # Carica anche le versioni versions_resp = api_client.get(f"/api/recipes/{recipe_id}/versions") if isinstance(versions_resp, list): recipe["versions"] = versions_resp else: recipe["versions"] = [] return render_template("maker/recipe_editor.html", recipe=recipe) @maker_bp.route("/recipes//tasks") @login_required @role_required("Maker") def task_editor(recipe_id: int): """Task/subtask editor with tolerances.""" # Carica recipe con task/subtask resp = api_client.get(f"/api/recipes/{recipe_id}") if resp.get("error"): flash(_("Errore nel caricamento della ricetta: %(error)s", error=resp.get("detail", "")), "error") return redirect(url_for("maker.recipe_list")) recipe = resp # Carica tasks con subtasks tasks_resp = api_client.get(f"/api/recipes/{recipe_id}/tasks") if isinstance(tasks_resp, list): recipe["tasks"] = tasks_resp else: recipe["tasks"] = [] return render_template("maker/task_editor.html", recipe=recipe) @maker_bp.route("/recipes//preview") @login_required @role_required("Maker") def recipe_preview(recipe_id: int): """Preview recipe as MeasurementTec would see it.""" # Carica recipe completo resp = api_client.get(f"/api/recipes/{recipe_id}") if resp.get("error"): flash(_("Errore nel caricamento della ricetta: %(error)s", error=resp.get("detail", "")), "error") return redirect(url_for("maker.recipe_list")) recipe = resp # Carica tasks con subtasks tasks_resp = api_client.get(f"/api/recipes/{recipe_id}/tasks") if isinstance(tasks_resp, list): recipe["tasks"] = tasks_resp else: recipe["tasks"] = [] return render_template("maker/recipe_preview.html", recipe=recipe) @maker_bp.route("/recipes//versions") @login_required @role_required("Maker") def version_history(recipe_id: int): """Version history with diff.""" # Carica recipe base resp = api_client.get(f"/api/recipes/{recipe_id}") if resp.get("error"): flash(_("Errore nel caricamento della ricetta: %(error)s", error=resp.get("detail", "")), "error") return redirect(url_for("maker.recipe_list")) recipe = resp # Carica versioni versions_resp = api_client.get(f"/api/recipes/{recipe_id}/versions") if isinstance(versions_resp, list): versions = versions_resp else: versions = [] flash(_("Errore nel caricamento delle versioni: %(error)s", error=versions_resp.get("detail", "") if isinstance(versions_resp, dict) else ""), "warning") return render_template("maker/version_history.html", recipe=recipe, versions=versions) # ============================================================================ # API PROXY AJAX (JSON) # ============================================================================ @maker_bp.route("/api/recipes", methods=["POST"]) @login_required @role_required("Maker") def api_create_recipe(): """Proxy: Create new recipe.""" data = request.get_json(silent=True) or {} resp = api_client.post("/api/recipes", data=data) if resp.get("error"): return jsonify(resp), resp.get("status_code", 500) return jsonify(resp), 201 @maker_bp.route("/api/recipes/", methods=["PUT"]) @login_required @role_required("Maker") def api_update_recipe(recipe_id: int): """Proxy: Update recipe (creates new version).""" data = request.get_json(silent=True) or {} resp = api_client.put(f"/api/recipes/{recipe_id}", data=data) if resp.get("error"): return jsonify(resp), resp.get("status_code", 500) return jsonify(resp), 200 @maker_bp.route("/api/recipes/", methods=["DELETE"]) @login_required @role_required("Maker") def api_delete_recipe(recipe_id: int): """Proxy: Delete recipe (soft delete).""" resp = api_client.delete(f"/api/recipes/{recipe_id}") if resp.get("error"): return jsonify(resp), resp.get("status_code", 500) return jsonify(resp), 200 # ============================================================================ # TASK API PROXY # ============================================================================ @maker_bp.route("/api/recipes//tasks", methods=["POST"]) @login_required @role_required("Maker") def api_create_task(recipe_id: int): """Proxy: Create new task.""" data = request.get_json(silent=True) or {} resp = api_client.post(f"/api/recipes/{recipe_id}/tasks", data=data) if resp.get("error"): return jsonify(resp), resp.get("status_code", 500) return jsonify(resp), 201 @maker_bp.route("/api/tasks/", methods=["PUT"]) @login_required @role_required("Maker") def api_update_task(task_id: int): """Proxy: Update task.""" data = request.get_json(silent=True) or {} resp = api_client.put(f"/api/tasks/{task_id}", data=data) if resp.get("error"): return jsonify(resp), resp.get("status_code", 500) return jsonify(resp), 200 @maker_bp.route("/api/tasks/", methods=["DELETE"]) @login_required @role_required("Maker") def api_delete_task(task_id: int): """Proxy: Delete task.""" resp = api_client.delete(f"/api/tasks/{task_id}") if resp.get("error"): return jsonify(resp), resp.get("status_code", 500) # 204 No Content returns empty dict return jsonify(resp), 204 @maker_bp.route("/api/tasks/reorder", methods=["PUT"]) @login_required @role_required("Maker") def api_reorder_tasks(): """Proxy: Reorder tasks.""" data = request.get_json(silent=True) or {} resp = api_client.put("/api/tasks/reorder", data=data) if resp.get("error"): return jsonify(resp), resp.get("status_code", 500) return jsonify(resp), 200 # ============================================================================ # SUBTASK API PROXY # ============================================================================ @maker_bp.route("/api/tasks//subtasks", methods=["POST"]) @login_required @role_required("Maker") def api_create_subtask(task_id: int): """Proxy: Create new subtask.""" data = request.get_json(silent=True) or {} resp = api_client.post(f"/api/tasks/{task_id}/subtasks", data=data) if resp.get("error"): return jsonify(resp), resp.get("status_code", 500) return jsonify(resp), 201 @maker_bp.route("/api/subtasks/", methods=["PUT"]) @login_required @role_required("Maker") def api_update_subtask(subtask_id: int): """Proxy: Update subtask.""" data = request.get_json(silent=True) or {} resp = api_client.put(f"/api/subtasks/{subtask_id}", data=data) if resp.get("error"): return jsonify(resp), resp.get("status_code", 500) return jsonify(resp), 200 @maker_bp.route("/api/subtasks/", methods=["DELETE"]) @login_required @role_required("Maker") def api_delete_subtask(subtask_id: int): """Proxy: Delete subtask.""" resp = api_client.delete(f"/api/subtasks/{subtask_id}") if resp.get("error"): return jsonify(resp), resp.get("status_code", 500) # 204 No Content returns empty dict return jsonify(resp), 204 # ============================================================================ # FILE API PROXY # ============================================================================ @maker_bp.route("/api/upload", methods=["POST"]) @login_required @role_required("Maker") def api_upload_file(): """Proxy: Upload file (multipart forward).""" # Controlla se c'รจ un file if "file" not in request.files: return jsonify({"error": True, "detail": _("Nessun file caricato")}), 400 file = request.files["file"] if file.filename == "": return jsonify({"error": True, "detail": _("Nome file vuoto")}), 400 # Prepara multipart data recipe_id = request.form.get("recipe_id") version_id = request.form.get("version_id") files = {"file": (file.filename, file.stream, file.content_type)} data = {} if recipe_id: data["recipe_id"] = recipe_id if version_id: data["version_id"] = version_id # Forward multipart request resp = api_client.post("/api/files/upload", data=data, files=files) if resp.get("error"): return jsonify(resp), resp.get("status_code", 500) return jsonify(resp), 201 @maker_bp.route("/api/files/", methods=["DELETE"]) @login_required @role_required("Maker") def api_delete_file(file_path: str): """Proxy: Delete file.""" resp = api_client.delete(f"/api/files/{file_path}") if resp.get("error"): return jsonify(resp), resp.get("status_code", 500) # 204 No Content returns empty dict return jsonify(resp), 204 # ============================================================================ # VERSION API PROXY # ============================================================================ @maker_bp.route("/api/recipes//versions//measurement-count", methods=["GET"]) @login_required @role_required("Maker") def api_get_measurement_count(recipe_id: int, version_number: int): """Proxy: Get measurement count for a specific version.""" resp = api_client.get(f"/api/recipes/{recipe_id}/versions/{version_number}/measurement-count") if resp.get("error"): return jsonify(resp), resp.get("status_code", 500) return jsonify(resp), 200