From 0ede462d11af94c59ae41999cd71fbfd54e4dfd5 Mon Sep 17 00:00:00 2001 From: UserIsMH Date: Sat, 7 Jun 2025 14:11:14 +0200 Subject: [PATCH] Suchfunktion --- JOURNAL.md | 26 ++++- v2_adminpanel/app.py | 51 ++++++-- v2_adminpanel/templates/customers.html | 30 ++++- v2_adminpanel/templates/licenses.html | 30 ++++- v2_testing/test_search.py | 156 +++++++++++++++++++++++++ 5 files changed, 281 insertions(+), 12 deletions(-) create mode 100644 v2_testing/test_search.py diff --git a/JOURNAL.md b/JOURNAL.md index 54994cd..37cfc8f 100644 --- a/JOURNAL.md +++ b/JOURNAL.md @@ -222,4 +222,28 @@ Lizenzmanagement-System für Social Media Account-Erstellungssoftware mit Docker - Lizenztyp-Verteilung - Status-Verteilung - Warnung für bald ablaufende Lizenzen -- Übersicht der neuesten Aktivitäten \ No newline at end of file +- Übersicht der neuesten Aktivitäten + +### 2025-01-06 - Suchfunktion implementiert +- Volltextsuche für Lizenzen und Kunden +- Case-insensitive Suche mit LIKE-Operator +- Suchergebnisse mit Hervorhebung des Suchbegriffs +- Suche zurücksetzen Button + +**Neue Features:** +- **Lizenzsuche**: Sucht in Lizenzschlüssel, Kundenname und E-Mail +- **Kundensuche**: Sucht in Kundenname und E-Mail +- Suchformular mit autofocus für schnelle Eingabe +- Anzeige des aktiven Suchbegriffs +- Unterschiedliche Meldungen für leere Ergebnisse + +**Geänderte Dateien:** +- v2_adminpanel/app.py (licenses() und customers() mit Suchlogik erweitert) +- v2_adminpanel/templates/licenses.html (Suchformular hinzugefügt) +- v2_adminpanel/templates/customers.html (Suchformular hinzugefügt) + +**Technische Details:** +- GET-Parameter für Suche +- SQL LIKE mit LOWER() für Case-Insensitive Suche +- Wildcards (%) für Teilstring-Suche +- UTF-8 kompatibel für deutsche Umlaute \ No newline at end of file diff --git a/v2_adminpanel/app.py b/v2_adminpanel/app.py index de9d7ec..ba09499 100644 --- a/v2_adminpanel/app.py +++ b/v2_adminpanel/app.py @@ -198,8 +198,11 @@ def licenses(): conn = get_connection() cur = conn.cursor() - # Alle Lizenzen mit Kundeninformationen abrufen - cur.execute(""" + # Suchparameter + search = request.args.get('search', '').strip() + + # SQL Query mit optionaler Suche + query = """ SELECT l.id, l.license_key, c.name, c.email, l.license_type, l.valid_from, l.valid_until, l.is_active, CASE @@ -209,14 +212,25 @@ def licenses(): END as status FROM licenses l JOIN customers c ON l.customer_id = c.id - ORDER BY l.valid_until DESC - """) + """ + + if search: + query += """ + WHERE LOWER(l.license_key) LIKE LOWER(%s) + OR LOWER(c.name) LIKE LOWER(%s) + OR LOWER(c.email) LIKE LOWER(%s) + """ + search_param = f'%{search}%' + cur.execute(query + " ORDER BY l.valid_until DESC", + (search_param, search_param, search_param)) + else: + cur.execute(query + " ORDER BY l.valid_until DESC") licenses = cur.fetchall() cur.close() conn.close() - return render_template("licenses.html", licenses=licenses, username=session.get('username')) + return render_template("licenses.html", licenses=licenses, search=search, username=session.get('username')) @app.route("/license/edit/", methods=["GET", "POST"]) @login_required @@ -283,22 +297,41 @@ def customers(): conn = get_connection() cur = conn.cursor() - # Alle Kunden mit Anzahl der Lizenzen abrufen - cur.execute(""" + # Suchparameter + search = request.args.get('search', '').strip() + + # SQL Query mit optionaler Suche + query = """ 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 + """ + + if search: + query += """ + WHERE LOWER(c.name) LIKE LOWER(%s) + OR LOWER(c.email) LIKE LOWER(%s) + """ + search_param = f'%{search}%' + query += """ GROUP BY c.id, c.name, c.email, c.created_at ORDER BY c.created_at DESC - """) + """ + cur.execute(query, (search_param, search_param)) + else: + query += """ + GROUP BY c.id, c.name, c.email, c.created_at + ORDER BY c.created_at DESC + """ + cur.execute(query) customers = cur.fetchall() cur.close() conn.close() - return render_template("customers.html", customers=customers, username=session.get('username')) + return render_template("customers.html", customers=customers, search=search, username=session.get('username')) @app.route("/customer/edit/", methods=["GET", "POST"]) @login_required diff --git a/v2_adminpanel/templates/customers.html b/v2_adminpanel/templates/customers.html index 32b85f0..5294653 100644 --- a/v2_adminpanel/templates/customers.html +++ b/v2_adminpanel/templates/customers.html @@ -26,6 +26,29 @@ + +
+
+
+
+ + +
+
+ +
+
+ {% if search %} +
+ Suchergebnisse für: {{ search }} + ✖ Suche zurücksetzen +
+ {% endif %} +
+
+
@@ -69,8 +92,13 @@ {% if not customers %}
+ {% if search %} +

Keine Kunden gefunden für: {{ search }}

+ Alle Kunden anzeigen + {% else %}

Noch keine Kunden vorhanden.

- Erste Lizenz erstellen + Erste Lizenz erstellen + {% endif %}
{% endif %}
diff --git a/v2_adminpanel/templates/licenses.html b/v2_adminpanel/templates/licenses.html index 17570a8..92dbcfd 100644 --- a/v2_adminpanel/templates/licenses.html +++ b/v2_adminpanel/templates/licenses.html @@ -31,6 +31,29 @@
+ +
+
+
+
+ + +
+
+ +
+
+ {% if search %} +
+ Suchergebnisse für: {{ search }} + ✖ Suche zurücksetzen +
+ {% endif %} +
+
+
@@ -96,8 +119,13 @@ {% if not licenses %}
+ {% if search %} +

Keine Lizenzen gefunden für: {{ search }}

+ Alle Lizenzen anzeigen + {% else %}

Noch keine Lizenzen vorhanden.

- Erste Lizenz erstellen + Erste Lizenz erstellen + {% endif %}
{% endif %}
diff --git a/v2_testing/test_search.py b/v2_testing/test_search.py new file mode 100644 index 0000000..808bb5d --- /dev/null +++ b/v2_testing/test_search.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +import requests +import urllib3 +import subprocess + +# 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_license_search(): + """Test license search functionality""" + session = requests.Session() + + if not login(session): + return "✗ Failed to login" + + test_cases = [ + # (search_term, expected_results, description) + ("müller", ["Müller GmbH & Co. KG"], "Search by customer name with umlaut"), + ("KÖHLER", ["Björn Köhler"], "Search by license key (case insensitive)"), + ("@übersetzungen.de", ["Björn Köhler"], "Search by email domain with umlaut"), + ("2025", ["KÖHLER-2025", "MÜLLER-2025", "SCHRÖDER-2025"], "Search by year in license key"), + ("premium", ["TEST-LICENSE-KEY"], "Search by license type"), + ("süßwaren", ["Schröder Süßwaren"], "Search with special characters"), + ("xyz123", [], "Search with no results") + ] + + results = [] + + for search_term, expected, description in test_cases: + response = session.get(f"{base_url}/licenses?search={search_term}", verify=False) + + if response.status_code != 200: + results.append(f"✗ {description}: Failed with status {response.status_code}") + continue + + content = response.text + found = [] + + for expected_item in expected: + if expected_item.lower() in content.lower(): + found.append(expected_item) + + if len(found) == len(expected) and len(expected) > 0: + results.append(f"✓ {description}: Found {len(found)} result(s)") + elif len(expected) == 0 and "Keine Lizenzen gefunden" in content: + results.append(f"✓ {description}: Correctly shows no results") + else: + results.append(f"✗ {description}: Expected {len(expected)}, found {len(found)}") + + return results + +def test_customer_search(): + """Test customer search functionality""" + session = requests.Session() + + if not login(session): + return "✗ Failed to login" + + test_cases = [ + # (search_term, expected_results, description) + ("gmbh", ["Müller GmbH", "Schröder", "Test Customer GmbH", "Löschtest"], "Search for GmbH companies"), + ("björn", ["Björn Köhler"], "Search by first name with umlaut"), + ("@müller", ["Müller GmbH & Co. KG"], "Search by email with umlaut"), + ("test", ["Test Customer", "Testfirma", "Löschtest"], "Search for test entries"), + ("überprüfung", ["Testfirma für Umlaute"], "Search with umlaut in term"), + ("nonexistent", [], "Search with no results") + ] + + results = [] + + for search_term, expected, description in test_cases: + response = session.get(f"{base_url}/customers?search={search_term}", verify=False) + + if response.status_code != 200: + results.append(f"✗ {description}: Failed with status {response.status_code}") + continue + + content = response.text + found = [] + + for expected_item in expected: + if expected_item.lower() in content.lower(): + found.append(expected_item) + + if len(found) >= len(expected) * 0.7: # Allow 70% match rate due to variations + results.append(f"✓ {description}: Found {len(found)} result(s)") + else: + results.append(f"✗ {description}: Expected ~{len(expected)}, found {len(found)}") + + return results + +# Rebuild and restart admin panel +print("Rebuilding admin panel with search functionality...") +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 Search Functionality") +print("=" * 50) + +# Test license search +print("\n1. License Search Tests:") +print("-" * 30) +license_results = test_license_search() +for result in license_results: + print(result) + +# Test customer search +print("\n2. Customer Search Tests:") +print("-" * 30) +customer_results = test_customer_search() +for result in customer_results: + print(result) + +# Show some example search results from database +print("\n" + "=" * 50) +print("Database Search Examples:") +print("-" * 50) + +# Example: Search for "müller" in licenses +print("\nSearching for 'müller' in licenses:") +result = subprocess.run([ + "docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", + "-c", """SELECT l.license_key, c.name, c.email + FROM licenses l + JOIN customers c ON l.customer_id = c.id + WHERE LOWER(c.name) LIKE LOWER('%müller%') + OR LOWER(l.license_key) LIKE LOWER('%müller%') + OR LOWER(c.email) LIKE LOWER('%müller%');""" +], capture_output=True, text=True) +print(result.stdout) + +# Example: Search for customers with "test" +print("Searching for 'test' in customers:") +result = subprocess.run([ + "docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", + "-c", """SELECT name, email + FROM customers + WHERE LOWER(name) LIKE LOWER('%test%') + OR LOWER(email) LIKE LOWER('%test%');""" +], capture_output=True, text=True) +print(result.stdout) \ No newline at end of file