From 2cea23e391ca6fd29d5edacf6e0be6db51789e78 Mon Sep 17 00:00:00 2001 From: AIIrondev Date: Tue, 25 Aug 2026 20:45:18 +0200 Subject: [PATCH] changes to the booking process --- Website/main.py | 153 ++++++++++++++++++++++++++++++------------------ 1 file changed, 96 insertions(+), 57 deletions(-) diff --git a/Website/main.py b/Website/main.py index dd408df..f291ce4 100644 --- a/Website/main.py +++ b/Website/main.py @@ -2166,11 +2166,87 @@ def book_option_package(): return redirect(url_for("booking_payment", packages=packages_raw, flow=booking_flow)) +def background_booking_provisioning(app_instance, prov_id, subdomain, modules_to_provision, user_id, school_name): + """Hintergrund-Worker für die klassische Buchung""" + with app_instance.app_context(): + req_client, req_col = _get_collection("instance_requests") + try: + # Status auf In Progress setzen + req_col.update_one({"_id": ObjectId(prov_id)}, {"$set": {"provision_status": "in_progress"}}) + + provision_error = "" + provision_config_ok = True + provision_port = None + admin_password = generate_secure_password() + + # 1. Shell Provisioning + try: + port = steering.instance.new(subdomain) + if port and port is not False: + try: + provision_port = int(port) if isinstance(port, (int, str)) and str(port).isdigit() else port + except Exception: + provision_port = port + + # 2. Gewählte Module aktivieren + for mod in modules_to_provision: + try: + edit_ok = steering.instance.edit(subdomain, mod) + if not edit_ok: + provision_config_ok = False + provision_error += f" | Edit failed for {mod}" + except Exception as e: + provision_config_ok = False + provision_error += f" | {mod}: {str(e)}" + else: + provision_error = "Provisioning script returned failure" + provision_config_ok = False + except Exception as e: + provision_error = str(e) + provision_config_ok = False + + # 3. Wenn erfolgreich, Instanz in die 'instances' Collection eintragen! (WICHTIG für Health-Check) + if provision_config_ok and not provision_error: + domain = f"{subdomain}.invario-software.de" + instance_doc = { + "owner_id": user_id, + "school_name": school_name, + "subdomain": subdomain, + "domain": domain, + "https_port": provision_port, + "status": "ready", + "nginx_status": "active", + "admin_username": "admin", + "admin_password": admin_password + } + inst_client, inst_col = _get_collection("instances") + try: + inst_col.insert_one(instance_doc) + finally: + inst_client.close() + + # 4. Finalen Status setzen ("ready" löst den Redirect im Frontend aus) + final_status = "ready" if provision_config_ok and not provision_error else "failed" + req_col.update_one( + {"_id": ObjectId(prov_id)}, + {"$set": { + "provision_status": final_status, + "provision_port": provision_port or "", + "provision_config_ok": provision_config_ok, + "provision_error": provision_error.strip(" | "), + "updated_at": _utc_now_iso() + }} + ) + except Exception as e: + print(f"[ERROR] Async Provisioning failed: {e}") + req_col.update_one({"_id": ObjectId(prov_id)}, {"$set": {"provision_status": "failed", "provision_error": str(e)}}) + finally: + req_client.close() + @app.route('/booking/payment', methods=['GET', 'POST']) @login_required def booking_payment(): - # Accept comma-separated list of items (e.g. "inventarsystem,buecherei,terminverwaltung") packages_str = _sanitize_text(request.values.get("packages") or request.values.get("package") or "", 200).lower() booking_flow = _sanitize_text(request.values.get("flow") or "payment", 24).lower() @@ -2183,7 +2259,6 @@ def booking_payment(): package_list = [p.strip() for p in packages_str.split(',') if p.strip()] - # Validate each option against BOOKING_PACKAGE_MAP and calculate total price selected_package_names = [] total_price = 0.0 for pkg in package_list: @@ -2195,9 +2270,8 @@ def booking_payment(): flash(f"Ungültige Buchungsoption: {pkg}", "error") return redirect(url_for("preise")) - # Combined label string for UI and DB (e.g., "Inventarsystem, Bücherei") selected_package = ", ".join(selected_package_names) - package_raw = packages_str # Raw keys string for DB + package_raw = packages_str flow_config = BOOKING_FLOW_MAP[booking_flow] @@ -2303,46 +2377,6 @@ def booking_payment(): return render_template("schnell_buchung.html", **review_data) subdomain = base[:63] - provision_status = "not_started" - provision_port = None - provision_error = "" - provision_config_ok = True - - try: - try: - port = steering.instance.new(subdomain) - except Exception as e: - port = False - provision_error = str(e) - provision_config_ok = False - - if port and port is not False: - provision_status = "started" - try: - provision_port = int(port) if isinstance(port, (int, str)) and str(port).isdigit() else port - except Exception: - provision_port = port - - # Provision all valid modules found in the list (e.g., "inventarsystem", "buecherei") - valid_modules = set(BOOKING_PACKAGE_MAP.keys()) - modules_to_provision = {pkg for pkg in package_list if pkg in valid_modules} - - for mod in modules_to_provision: - try: - edit_ok = steering.instance.edit(subdomain, mod) - if not edit_ok: - provision_config_ok = False - provision_error += f" | Edit failed for {mod}" - except Exception as e: - provision_config_ok = False - provision_error += f" | {mod}: {str(e)}" - else: - provision_status = "failed" - if not provision_error: - provision_error = "Provisioning script returned failure" - except Exception as e: - provision_status = "failed" - provision_error = str(e) provision_payload = { "username": session.get("username"), @@ -2356,21 +2390,26 @@ def booking_payment(): "assigned_admin_display_name": assigned_admin_display_name, "assigned_admin_email": assigned_admin_email, "subdomain": subdomain, - "provision_status": provision_status, - "provision_port": provision_port or "", - "provision_config_ok": provision_config_ok, - "provision_error": provision_error.strip(" | "), + "tenant_slug": subdomain, + "provision_status": "pending", "created_at": _utc_now_iso(), "source": "booking_payment", } - req_client = None + req_client, req_col = _get_collection("instance_requests") try: - req_client, req_col = _get_collection("instance_requests") - req_col.insert_one(provision_payload) + prov_id = str(req_col.insert_one(provision_payload).inserted_id) finally: - if req_client: - req_client.close() + req_client.close() + + valid_modules = set(BOOKING_PACKAGE_MAP.keys()) + modules_to_provision = {pkg for pkg in package_list if pkg in valid_modules} + + thread = threading.Thread( + target=background_booking_provisioning, + args=(app, prov_id, subdomain, modules_to_provision, session.get("user_id"), form_data["school_name"]) + ) + thread.start() except PyMongoError: flash("Buchungsanfrage konnte nicht gesendet werden. Bitte versuchen Sie es erneut.", "error") @@ -2382,7 +2421,7 @@ def booking_payment(): success_text = ( f"Gesprächstermin für {selected_package} wurde an das Team gesendet." if booking_flow == "consultation" - else f"Buchung für {selected_package} wurde erfolgreich erfasst. Die Rechnung wird per E-Mail versendet." + else f"Buchung für {selected_package} wird eingerichtet. Sie werden gleich weitergeleitet." ) flash(success_text, "success") @@ -3789,10 +3828,11 @@ def tenant_status(): finally: req_client.close() - if not prov_req or prov_req.get("provision_status") != "completed": + current_status = prov_req.get("provision_status") if prov_req else None + + if not prov_req or current_status not in ["ready", "completed"]: return jsonify({"status": "pending"}), 200 - # Retrieve created instance details for health check inst_client, inst_col = _get_collection("instances") try: instance = inst_col.find_one({"subdomain": prov_req.get("tenant_slug")}) @@ -3802,7 +3842,6 @@ def tenant_status(): if not instance: return jsonify({"status": "pending"}), 200 - # Perform health check ping target_url = f"https://{instance['domain']}/health" try: response = requests.get(target_url, timeout=3)