""" CRUD определений сценариев. GET /api/scenario/definitions — список POST /api/scenario/definitions — создать GET /api/scenario/definitions/ — один PUT /api/scenario/definitions/ — обновить (version → 409) DELETE /api/scenario/definitions/ — мягкое удаление """ from flask import Blueprint, jsonify, request from api.auth import get_client_id, get_stand, get_token_info from db.scenario_defs import ( list_definitions, get_definition, create_definition, update_definition, delete_definition, ) bp_defs = Blueprint("api_scenario_defs", __name__) def _validate_steps(steps): """Проверить структуру шагов сценария. Возвращает None или str с ошибкой.""" if not isinstance(steps, list) or len(steps) == 0: return "Шаги: непустой массив" outputs = {} # output_name → step_index (для проверки ссылок и уникальности) for i, s in enumerate(steps): if not isinstance(s, dict): return f"Шаг {i+1}: должен быть объектом" svc_id = s.get("service_id") if not isinstance(svc_id, int) or svc_id <= 0: return f"Шаг {i+1}: service_id — положительный int" op = (s.get("operation") or "").strip() if not op: return f"Шаг {i+1}: operation обязателен" if not isinstance(s.get("params", {}), dict): return f"Шаг {i+1}: params — объект" # Новые опциональные ключи: output, instance_ref, instance_uid out = (s.get("output") or "").strip() ref = (s.get("instance_ref") or "").strip() uid = (s.get("instance_uid") or "").strip() # output — только для create, уникален if out: if op != "create": return f"Шаг {i+1}: output допустим только для create" if out in outputs: return f"Шаг {i+1}: output '{out}' уже используется в шаге {outputs[out]+1}" outputs[out] = i # instance_ref — должен ссылаться на существующий output из предыдущих шагов if ref: if ref not in outputs: return f"Шаг {i+1}: instance_ref '{ref}' — output не найден (должен быть в create-шаге выше)" # Для не-create: должен быть instance_ref, instance_uid, или старый формат (fallback) if op != "create": has_target = bool(ref or uid or (not out)) # out в не-create — ошибка выше # ok: ref есть, uid есть, или старый формат без новых ключей (fallback) return None @bp_defs.route("/api/scenario/definitions", methods=["GET", "POST"]) def api_definitions(): cid = get_client_id() stand = get_stand() if request.method == "GET": try: defs = list_definitions(cid, stand) return jsonify(defs) except Exception as e: return jsonify({"error": str(e)}), 500 if request.method == "POST": try: data = request.get_json() name = (data.get("name") or "").strip() if not name: return jsonify({"error": "name is required"}), 400 steps = data.get("steps", []) err = _validate_steps(steps) if err: return jsonify({"error": err}), 400 user = get_token_info().get("email", "") result = create_definition(cid, stand, name, steps, user) if result is None: return jsonify({"error": "create failed"}), 500 return jsonify(result), 201 except Exception as e: return jsonify({"error": str(e)}), 500 @bp_defs.route("/api/scenario/definitions/", methods=["GET", "PUT", "DELETE"]) def api_definition(def_id): cid = get_client_id() stand = get_stand() if request.method == "GET": d = get_definition(def_id, cid, stand) if not d: return jsonify({"error": "not found"}), 404 return jsonify(d) if request.method == "PUT": try: data = request.get_json() name = (data.get("name") or "").strip() version = data.get("version") if not name: return jsonify({"error": "name is required"}), 400 if not isinstance(version, int): return jsonify({"error": "version (int) is required"}), 400 steps = data.get("steps", []) err = _validate_steps(steps) if err: return jsonify({"error": err}), 400 user = get_token_info().get("email", "") result = update_definition(def_id, cid, stand, name, steps, version, user) if result.get("conflict"): return jsonify({"error": "Conflict: definition was modified"}), 409 if result.get("error"): return jsonify({"error": result["error"]}), 500 return jsonify(result) except Exception as e: return jsonify({"error": str(e)}), 500 if request.method == "DELETE": ok = delete_definition(def_id, cid, stand) if not ok: return jsonify({"error": "not found"}), 404 return jsonify({"ok": True})