removal of redundant data

This commit is contained in:
2026-08-26 12:39:27 +02:00
parent 1bc96ae2e6
commit 261d29abf6
+36 -153
View File
@@ -2168,7 +2168,31 @@ 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, user_email):
ALL_MODULE_KEYS = ["library", "inventory", "student_cards", "terminplan"]
PACKAGE_MODULE_MAP = {
"buecherei": ["library"],
"inventarsystem": ["inventory"],
"terminverwaltung": ["terminplan"],
"starter": ["library", "inventory", "student_cards"],
"advanced": ["library", "inventory", "student_cards", "terminplan"],
}
def build_module_config_string(package_list: list) -> str:
active_modules = set()
for pkg in package_list:
pkg_key = pkg.strip().lower()
if pkg_key in ALL_MODULE_KEYS:
active_modules.add(pkg_key)
elif pkg_key in PACKAGE_MODULE_MAP:
active_modules.update(PACKAGE_MODULE_MAP[pkg_key])
config_parts = [
f"{mod}={'on' if mod in active_modules else 'off'}"
for mod in ALL_MODULE_KEYS
]
return " ".join(config_parts)
def background_booking_provisioning(app_instance, prov_id, subdomain, package_list, user_id, school_name, user_email):
"""Hintergrund-Worker, der die Tenant-Bereitstellung über das Host-Watcher-Triggersystem auslöst."""
with app_instance.app_context():
req_client, req_col = _get_collection("instance_requests")
@@ -2191,23 +2215,26 @@ def background_booking_provisioning(app_instance, prov_id, subdomain, modules_to
next_port = max(used_ports) + 1 if used_ports else 10002
admin_password = generate_secure_password()
# 3. Trigger-Datei fuer den Host-Watcher schreiben (ersetzt Instance.new & Instance.edit)
# --- NEU: Generiere den korrekten Modul-String ---
module_config_str = build_module_config_string(package_list)
# 3. Trigger-Datei fuer den Host-Watcher schreiben
os.makedirs(TRIGGER_DIR, exist_ok=True)
trigger_file = os.path.join(TRIGGER_DIR, f"{subdomain}.json")
trigger_payload = {
"action": "add", # <--- GANZ WICHTIG FÜR DEN WATCHER
"request_id": str(prov_id),
"slug": subdomain,
"port": next_port,
"password": admin_password,
"modules": list(modules_to_provision)
"module_config": module_config_str # <--- ERSETZT "modules": list(...)
}
with open(trigger_file, "w") as f:
json.dump(trigger_payload, f)
print(f"[INFO] Trigger-Datei fuer Host-Watcher erstellt: {trigger_file}")
print(f"[INFO] Trigger-Datei fuer Host-Watcher erstellt: {trigger_file} mit Payload: {trigger_payload}")
# 4. Instanz-Eintrag in der Datenbank registrieren
domain = f"{subdomain}.invario-software.de"
instance_doc = {
@@ -2438,17 +2465,14 @@ def booking_payment():
finally:
req_client.close()
valid_modules = set(BOOKING_PACKAGE_MAP.keys())
modules_to_provision = {pkg for pkg in package_list if pkg in valid_modules}
app_obj = current_app._get_current_object()
user_id = session.get("user_id")
user_email = session.get("email") or form_data.get("contact_email")
# Startet den asynchronen Thread, der die Trigger-Datei ablegt
# Startet den asynchronen Thread. Wir übergeben nun direkt die 'package_list'
thread = threading.Thread(
target=background_booking_provisioning,
args=(app_obj, prov_id, subdomain, modules_to_provision, user_id, form_data["school_name"], user_email)
args=(app_obj, prov_id, subdomain, package_list, user_id, form_data["school_name"], user_email)
)
thread.start()
print(f"[INFO] Background Provisioning Trigger gestartet für Subdomain: {subdomain}")
@@ -3709,149 +3733,6 @@ def generate_secure_password(length=14):
def _utc_now_iso():
return datetime.now(timezone.utc).isoformat()
def _update_request_status(request_id, status, error_message=None):
"""
Updates the status of a provisioning request in the database.
"""
client, col = _get_collection("instance_requests")
try:
update_fields = {
"provision_status": status,
"updated_at": datetime.utcnow()
}
if error_message:
update_fields["last_error"] = error_message
col.update_one(
{"_id": ObjectId(request_id)},
{"$set": update_fields}
)
except Exception as e:
print(f"[ERROR] Failed to update request status: {e}")
finally:
client.close()
def _create_instance_record(tenant_slug, port, admin_password):
"""
Creates the final database record for the active instance so the
frontend knows the domain, port, and credentials.
"""
client, col = _get_collection("instances")
try:
# Prevent duplicates if the watcher re-runs
existing = col.find_one({"subdomain": tenant_slug})
if existing:
return existing["_id"]
# Fetch the base domain from env variables, fallback to localhost if missing
parent_domain = os.environ.get("INSTANCE_PARENT_DOMAIN", "localhost")
full_domain = f"{tenant_slug}.{parent_domain}"
new_instance = {
"subdomain": tenant_slug,
"domain": full_domain,
"port": int(port),
"admin_username": "admin",
"admin_password": admin_password,
"created_at": datetime.utcnow(),
"status": "active"
}
result = col.insert_one(new_instance)
return result.inserted_id
except Exception as e:
print(f"[ERROR] Failed to create instance record: {e}")
finally:
client.close()
def run_tenant_provisioning_async(tenant_slug, next_port, admin_password, user_email, request_id):
"""
Background thread running inside the Flask container.
Drops a trigger payload for the host watcher service.
"""
try:
# 1. Update DB to provisioning state
_update_request_status(request_id, "provisioning")
# 2. Write trigger file for the host watcher
os.makedirs(TRIGGER_DIR, exist_ok=True)
trigger_file = os.path.join(TRIGGER_DIR, f"{tenant_slug}.json")
payload = {
"request_id": str(request_id),
"slug": tenant_slug,
"port": next_port,
"password": admin_password
}
with open(trigger_file, "w") as f:
json.dump(payload, f)
# 3. Register instance record in MongoDB
_create_instance_record(tenant_slug, next_port, admin_password)
# 4. Optional: Send accreditation email safely
try:
send_accreditation_email(
recipient=user_email,
domain=f"{tenant_slug}.yourdomain.com",
username="admin",
password=admin_password
)
except Exception as mail_err:
print(f"[WARN] Mail delivery failed (non-fatal): {mail_err}")
# Mark request as ready
_update_request_status(request_id, "ready")
except Exception as e:
print(f"[ERROR] Tenant provisioning failed: {e}")
_update_request_status(request_id, "failed", error_message=str(e))
@app.route('/booking_payment_async', methods=['POST'])
@login_required
def booking_payment_async():
try:
user_id = session.get("user_id")
user_email = session.get("email")
tenant_slug = _slugify_subdomain(request.form.get('tenant_slug'))
school_name = request.form.get('school_name', tenant_slug)
if not tenant_slug:
return jsonify({"success": False, "error": "Ungültige Subdomain angegeben."}), 400
# Datenbank-Eintrag erstellen...
req_client, req_col = _get_collection("instance_requests")
try:
prov_doc = {
"user_id": user_id,
"tenant_slug": tenant_slug,
"school_name": school_name,
"provision_status": "pending",
"created_at": _utc_now_iso(),
"updated_at": _utc_now_iso()
}
prov_id = str(req_col.insert_one(prov_doc).inserted_id)
finally:
req_client.close()
# Asynchronen Thread starten...
thread = threading.Thread(
target=run_tenant_provisioning_async,
args=(app, user_id, tenant_slug, school_name, user_email, prov_id)
)
thread.start()
return jsonify({
"success": True,
"tenant_slug": tenant_slug,
"request_id": prov_id
}), 200
except Exception as e:
# WICHTIG: Fängt jegliche Python-Fehler ab und sendet sauberes JSON statt HTML-Crash
return jsonify({"success": False, "error": str(e)}), 500
@app.route('/instance_request/status', methods=['GET'])
def tenant_status():
"""
@@ -3913,6 +3794,8 @@ def tenant_status():
pass
return jsonify({"status": "pending"}), 200
def main():
app.run(host="0.0.0.0", port=4999, debug=False)