diff --git a/JOURNAL.md b/JOURNAL.md index 8a2dd11..4929356 100644 --- a/JOURNAL.md +++ b/JOURNAL.md @@ -168,4 +168,29 @@ Lizenzmanagement-System für Social Media Account-Erstellungssoftware mit Docker **Sicherheit:** - Login-Required für alle Aktionen - POST-only für Löschvorgänge -- Bestätigungsdialog vor dem Löschen \ No newline at end of file +- Bestätigungsdialog vor dem Löschen + +### 2025-01-06 - Kundenverwaltung implementiert +- Komplette CRUD-Funktionalität für Kunden +- Übersicht zeigt Anzahl aktiver/gesamter Lizenzen pro Kunde +- Kunden können nur gelöscht werden, wenn sie keine Lizenzen haben +- Bearbeitungsseite zeigt alle Lizenzen des Kunden + +**Neue Features:** +- `/customers` - Kundenübersicht mit Statistiken +- `/customer/edit/` - Kunde bearbeiten (Name, E-Mail) +- `/customer/delete/` - Kunde löschen (nur ohne Lizenzen) +- Navigation zwischen allen drei Hauptbereichen +- Anzeige der Kundenlizenzen beim Bearbeiten + +**Geänderte/Neue Dateien:** +- v2_adminpanel/app.py (customers, edit_customer, delete_customer Routen) +- v2_adminpanel/templates/customers.html (neu erstellt) +- v2_adminpanel/templates/edit_customer.html (neu erstellt) +- v2_adminpanel/templates/index.html (Navigation erweitert) +- v2_adminpanel/templates/licenses.html (Navigation erweitert) + +**Besonderheiten:** +- Lösch-Button ist deaktiviert, wenn Kunde Lizenzen hat +- Aktive Lizenzen werden separat gezählt (nicht abgelaufen + aktiv) +- UTF-8 Support für Kundennamen mit Umlauten \ No newline at end of file diff --git a/v2_adminpanel/app.py b/v2_adminpanel/app.py index 9a6deec..ef7cc8b 100644 --- a/v2_adminpanel/app.py +++ b/v2_adminpanel/app.py @@ -185,5 +185,103 @@ def delete_license(license_id): return redirect("/licenses") +@app.route("/customers") +@login_required +def customers(): + conn = get_connection() + cur = conn.cursor() + + # Alle Kunden mit Anzahl der Lizenzen abrufen + cur.execute(""" + SELECT c.id, c.name, c.email, c.created_at, + COUNT(l.id) as license_count, + COUNT(CASE WHEN l.is_active = TRUE AND l.valid_until >= CURRENT_DATE THEN 1 END) as active_licenses + FROM customers c + LEFT JOIN licenses l ON c.id = l.customer_id + GROUP BY c.id, c.name, c.email, c.created_at + ORDER BY c.created_at DESC + """) + + customers = cur.fetchall() + cur.close() + conn.close() + + return render_template("customers.html", customers=customers, username=session.get('username')) + +@app.route("/customer/edit/", methods=["GET", "POST"]) +@login_required +def edit_customer(customer_id): + conn = get_connection() + cur = conn.cursor() + + if request.method == "POST": + # Update customer + name = request.form["name"] + email = request.form["email"] + + cur.execute(""" + UPDATE customers + SET name = %s, email = %s + WHERE id = %s + """, (name, email, customer_id)) + + conn.commit() + cur.close() + conn.close() + + return redirect("/customers") + + # Get customer data with licenses + cur.execute(""" + SELECT id, name, email, created_at + FROM customers + WHERE id = %s + """, (customer_id,)) + + customer = cur.fetchone() + + # Get customer's licenses + cur.execute(""" + SELECT id, license_key, license_type, valid_from, valid_until, is_active + FROM licenses + WHERE customer_id = %s + ORDER BY valid_until DESC + """, (customer_id,)) + + licenses = cur.fetchall() + + cur.close() + conn.close() + + if not customer: + return redirect("/customers") + + return render_template("edit_customer.html", customer=customer, licenses=licenses, username=session.get('username')) + +@app.route("/customer/delete/", methods=["POST"]) +@login_required +def delete_customer(customer_id): + conn = get_connection() + cur = conn.cursor() + + # Prüfen ob Kunde Lizenzen hat + cur.execute("SELECT COUNT(*) FROM licenses WHERE customer_id = %s", (customer_id,)) + license_count = cur.fetchone()[0] + + if license_count > 0: + # Kunde hat Lizenzen - nicht löschen + cur.close() + conn.close() + return redirect("/customers") + + # Kunde löschen wenn keine Lizenzen vorhanden + cur.execute("DELETE FROM customers WHERE id = %s", (customer_id,)) + + conn.commit() + cur.close() + conn.close() + + return redirect("/customers") + if __name__ == "__main__": app.run(host="0.0.0.0", port=443, ssl_context='adhoc') diff --git a/v2_adminpanel/templates/customers.html b/v2_adminpanel/templates/customers.html new file mode 100644 index 0000000..ba784b0 --- /dev/null +++ b/v2_adminpanel/templates/customers.html @@ -0,0 +1,80 @@ + + + + + Kundenverwaltung - Admin Panel + + + + + +
+
+

Kundenverwaltung

+ +
+ +
+
+
+ + + + + + + + + + + + + {% for customer in customers %} + + + + + + + + + {% endfor %} + +
IDNameE-MailErstellt amLizenzen (Aktiv/Gesamt)Aktionen
{{ customer[0] }}{{ customer[1] }}{{ customer[2] or '-' }}{{ customer[3].strftime('%d.%m.%Y %H:%M') }} + {{ customer[5] }}/{{ customer[4] }} + +
+ ✏️ Bearbeiten + {% if customer[4] == 0 %} +
+ +
+ {% else %} + + {% endif %} +
+
+ + {% if not customers %} +
+

Noch keine Kunden vorhanden.

+ Erste Lizenz erstellen +
+ {% endif %} +
+
+
+
+ + \ No newline at end of file diff --git a/v2_adminpanel/templates/edit_customer.html b/v2_adminpanel/templates/edit_customer.html new file mode 100644 index 0000000..a81e1b6 --- /dev/null +++ b/v2_adminpanel/templates/edit_customer.html @@ -0,0 +1,104 @@ + + + + + Kunde bearbeiten - Admin Panel + + + + + +
+
+

Kunde bearbeiten

+ ← Zurück zur Übersicht +
+ +
+
+
+
+
+ + +
+
+ + +
+
+ +

{{ customer[3].strftime('%d.%m.%Y %H:%M') }}

+
+
+ +
+ + Abbrechen +
+
+
+
+ +
+
+
Lizenzen des Kunden
+
+
+ {% if licenses %} +
+ + + + + + + + + + + + + {% for license in licenses %} + + + + + + + + + {% endfor %} + +
LizenzschlüsselTypGültig vonGültig bisAktivAktionen
{{ license[1] }} + {% if license[2] == 'full' %} + Vollversion + {% else %} + Testversion + {% endif %} + {{ license[3].strftime('%d.%m.%Y') }}{{ license[4].strftime('%d.%m.%Y') }} + {% if license[5] %} + + {% else %} + + {% endif %} + + Bearbeiten +
+
+ {% else %} +

Dieser Kunde hat noch keine Lizenzen.

+ {% endif %} +
+
+
+ + \ No newline at end of file diff --git a/v2_adminpanel/templates/index.html b/v2_adminpanel/templates/index.html index eee5257..630ec4c 100644 --- a/v2_adminpanel/templates/index.html +++ b/v2_adminpanel/templates/index.html @@ -19,7 +19,10 @@

Neue Lizenz erstellen

- 📋 Lizenzübersicht +
diff --git a/v2_adminpanel/templates/licenses.html b/v2_adminpanel/templates/licenses.html index c58575e..bd13050 100644 --- a/v2_adminpanel/templates/licenses.html +++ b/v2_adminpanel/templates/licenses.html @@ -24,7 +24,10 @@
diff --git a/v2_testing/test_customer_management.py b/v2_testing/test_customer_management.py new file mode 100644 index 0000000..b8b0a93 --- /dev/null +++ b/v2_testing/test_customer_management.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +import requests +import urllib3 +import subprocess +from datetime import datetime + +# Disable SSL warnings for self-signed certificate +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +# Test configuration +base_url = "https://localhost:443" +admin_user = {"username": "rac00n", "password": "1248163264"} + +def login(session): + """Login to admin panel""" + login_data = { + "username": admin_user["username"], + "password": admin_user["password"] + } + response = session.post(f"{base_url}/login", data=login_data, verify=False, allow_redirects=False) + return response.status_code == 302 + +def test_customer_list(): + """Test customer list view""" + session = requests.Session() + + if not login(session): + return "✗ Failed to login" + + response = session.get(f"{base_url}/customers", verify=False) + if response.status_code != 200: + return f"✗ Failed to access customers page: Status {response.status_code}" + + content = response.text + + # Check for expected customers + expected_customers = ["Müller GmbH", "Test Customer", "Schröder Süßwaren AG", "Björn Köhler"] + found_customers = [] + + for customer in expected_customers: + if customer in content: + found_customers.append(customer) + + if len(found_customers) > 0: + return f"✓ Customer list loaded successfully ({len(found_customers)} customers found)" + else: + return "✗ No customers found in list" + +def test_edit_customer(): + """Test editing a customer""" + session = requests.Session() + + if not login(session): + return "✗ Failed to login" + + # Get a customer ID to edit (let's find "Test Customer") + result = subprocess.run([ + "docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", "-t", + "-c", "SELECT id FROM customers WHERE name = 'Test Customer' LIMIT 1;" + ], capture_output=True, text=True) + + customer_id = result.stdout.strip() + if not customer_id: + return "✗ Failed to find test customer" + + # Test GET edit page + response = session.get(f"{base_url}/customer/edit/{customer_id}", verify=False) + if response.status_code != 200: + return f"✗ Failed to access customer edit page: Status {response.status_code}" + + # Check if customer data and licenses are displayed + content = response.text + if "Test Customer" not in content: + return "✗ Edit page doesn't show customer data" + + # Update customer data + updated_data = { + "name": "Test Customer GmbH & Co. KG", + "email": "updated@testcustomer.de" + } + + response = session.post(f"{base_url}/customer/edit/{customer_id}", + data=updated_data, + verify=False, + allow_redirects=False) + + if response.status_code == 302 and response.headers.get('Location') == '/customers': + return "✓ Customer edited successfully" + else: + return f"✗ Failed to edit customer: Status {response.status_code}" + +def test_delete_customer(): + """Test deleting a customer""" + session = requests.Session() + + if not login(session): + return "✗ Failed to login" + + # Create a test customer without licenses + test_customer = { + "customer_name": "Löschtest Firma GmbH", + "email": "delete@löschtest.de", + "license_key": "TEMP-KEY-DELETE", + "license_type": "Test", + "valid_from": "2025-01-01", + "valid_until": "2025-01-02" + } + + # Create customer with license + session.post(f"{base_url}/", data=test_customer, verify=False, allow_redirects=False) + + # Get the customer ID + result = subprocess.run([ + "docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", "-t", + "-c", "SELECT id FROM customers WHERE name = 'Löschtest Firma GmbH';" + ], capture_output=True, text=True) + + customer_id = result.stdout.strip() + if not customer_id: + return "✗ Failed to create test customer" + + # First, try to delete customer with license (should fail) + response = session.post(f"{base_url}/customer/delete/{customer_id}", + verify=False, + allow_redirects=False) + + # Check if customer still exists (should not be deleted due to license) + result = subprocess.run([ + "docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", "-t", + "-c", f"SELECT COUNT(*) FROM customers WHERE id = {customer_id};" + ], capture_output=True, text=True) + + if int(result.stdout.strip()) != 1: + return "✗ Customer with licenses was incorrectly deleted" + + # Now delete the license first + subprocess.run([ + "docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", + "-c", f"DELETE FROM licenses WHERE customer_id = {customer_id};" + ], capture_output=True) + + # Try to delete customer again (should work now) + response = session.post(f"{base_url}/customer/delete/{customer_id}", + verify=False, + allow_redirects=False) + + # Verify deletion + result = subprocess.run([ + "docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", "-t", + "-c", f"SELECT COUNT(*) FROM customers WHERE id = {customer_id};" + ], capture_output=True, text=True) + + if int(result.stdout.strip()) == 0: + return "✓ Customer deletion works correctly (protects customers with licenses)" + else: + return "✗ Failed to delete customer without licenses" + +# Rebuild and restart admin panel +print("Rebuilding admin panel with customer management...") +subprocess.run(["docker-compose", "build", "admin-panel"], capture_output=True) +subprocess.run(["docker-compose", "up", "-d", "admin-panel"], capture_output=True) +print("Waiting for container to start...") +subprocess.run(["sleep", "5"], capture_output=True) + +print("\nTesting Customer Management Functionality") +print("=" * 50) + +# Test customer list +print("\n1. Testing Customer List:") +print("-" * 30) +list_result = test_customer_list() +print(list_result) + +# Test edit functionality +print("\n2. Testing Customer Edit:") +print("-" * 30) +edit_result = test_edit_customer() +print(edit_result) + +# Verify the edit +print("\nVerifying edited customer:") +result = subprocess.run([ + "docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", + "-c", "SELECT name, email FROM customers WHERE name LIKE 'Test Customer%';" +], capture_output=True, text=True) +print(result.stdout) + +# Test delete functionality +print("\n3. Testing Customer Delete:") +print("-" * 30) +delete_result = test_delete_customer() +print(delete_result) + +# Show customer statistics +print("\n" + "=" * 50) +print("Customer Statistics:") +print("-" * 50) +result = subprocess.run([ + "docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", + "-c", """SELECT c.name, COUNT(l.id) as total_licenses, + COUNT(CASE WHEN l.is_active = TRUE AND l.valid_until >= CURRENT_DATE THEN 1 END) as active_licenses + FROM customers c + LEFT JOIN licenses l ON c.id = l.customer_id + GROUP BY c.name + ORDER BY c.name;""" +], capture_output=True, text=True) +print(result.stdout) \ No newline at end of file