2 Commits

3 changed files with 146 additions and 3 deletions

View File

@@ -34,6 +34,26 @@ Der vollautomatische "Zero Touch" Workflow für Trading Twins Anfragen.
* **Abstand:** Bietet zwei Termine an, mit ca. **3 Stunden Pause** dazwischen. * **Abstand:** Bietet zwei Termine an, mit ca. **3 Stunden Pause** dazwischen.
* **Buchung:** Klick auf Link -> Server erstellt Outlook-Termin von `info@` mit `e.melcer` als Teilnehmer. * **Buchung:** Klick auf Link -> Server erstellt Outlook-Termin von `info@` mit `e.melcer` als Teilnehmer.
### 6. Smartlead Webhook Integration (NEU)
* **Zweck:** Empfang von Echtzeit-Lead-Benachrichtigungen direkt aus der Smartlead-Plattform.
* **Event-basiert:** Das System empfängt nur Daten für *neue* Ereignisse (z.B. "Lead als heiß markiert"), keine historischen Daten.
#### Endpunkte
Die folgenden Endpunkte werden für Smartlead bereitgestellt. Die Basis-URL ist flexibel und kann bei einem Serverumzug einfach angepasst werden. Der Platzhalter `{IHRE_AKTUELLE_DOMAIN}` sollte durch die aktuell aktive URL (z.B. `floke-ai.duckdns.org` oder die zukünftige neue Domain) ersetzt werden.
* **Hot Leads:** `https://{IHRE_AKTUELLE_DOMAIN}/public/smartlead/hot-lead`
* **Follow-up Leads:** `https://{IHRE_AKTUELLE_DOMAIN}/public/smartlead/follow-up-lead`
#### Inbetriebnahme & Test-Prozess
Die Integration wird in einem kontrollierten Prozess ausgerollt, um die Datenstruktur sicher zu analysieren:
1. **URLs an Smartlead übergeben:** Die oben genannten URLs werden an den Smartlead-Support oder in deren Admin-Oberfläche eingetragen.
2. **Test-Auslösung anfordern:** Smartlead wird gebeten, für jeden der beiden Endpunkte einen einzelnen Test-Lead manuell auszulösen.
3. **Datenanalyse:** Alle eingehenden Anfragen werden ungefiltert in die Log-Datei `lead-engine/Log/smartlead_webhooks.log` geschrieben. Dies ermöglicht eine genaue Analyse der von Smartlead gesendeten JSON-Struktur.
4. **Implementierung der Logik:** Basierend auf den analysierten Test-Daten wird die eigentliche Geschäftslogik (z.B. Eintrag in die Datenbank, Teams-Benachrichtigung) implementiert.
Dieser Prozess stellt sicher, dass wir nicht "blind" entwickeln, sondern auf Basis der realen Datenstruktur von Smartlead aufbauen.
## 🏗 Architektur ## 🏗 Architektur
```text ```text
@@ -71,6 +91,68 @@ Der vollautomatische "Zero Touch" Workflow für Trading Twins Anfragen.
* **Problem:** Die API-Endpoints gaben nackten Text zurück, was für Kunden unprofessionell wirkte. * **Problem:** Die API-Endpoints gaben nackten Text zurück, was für Kunden unprofessionell wirkte.
* **Lösung:** Der Server liefert nun saubere HTML-Snippets zurück. Durch Setzen der `WORDPRESS_BOOKING_URL` in der `.env` führen die Links in der Kunden-E-Mail direkt auf die Kunden-Website. Dort wird das HTML-Ergebnis (Erfolg inkl. Teams-Link ODER Fallback zum Microsoft Bookings Kalender bei Doppelbuchung) unsichtbar in einem iFrame geladen. * **Lösung:** Der Server liefert nun saubere HTML-Snippets zurück. Durch Setzen der `WORDPRESS_BOOKING_URL` in der `.env` führen die Links in der Kunden-E-Mail direkt auf die Kunden-Website. Dort wird das HTML-Ergebnis (Erfolg inkl. Teams-Link ODER Fallback zum Microsoft Bookings Kalender bei Doppelbuchung) unsichtbar in einem iFrame geladen.
#### 5.1 Troubleshooting der iFrame-Integration
Um sicherzustellen, dass die Buchungs-Landingpage auf WordPress immer korrekt funktioniert sowohl mit spezifischen Terminvorschlägen als auch als Fallback zu einer allgemeinen Buchungsseite sind mehrere Konfigurationsschritte und Fehlerbehebungen notwendig:
**1. Robuster iFrame-Code für Ihre WordPress-Seite**
Der folgende JavaScript-Code sollte in Ihre WordPress-Seite integriert werden, die als Landingpage für Terminbuchungen dient (z.B. `robo-planet.de/terminbestaetigung`). Er prüft, ob spezifische Termindaten (Job-UUID und Timestamp) über die URL übergeben werden, und lädt entsprechend entweder den spezifischen Termin-Slot oder die allgemeine Microsoft Bookings URL als Fallback.
```html
<iframe id="booking-iframe" width="100%" height="800px" style="border:none;" scrolling="no"></iframe>
<script>
const urlParams = new URLSearchParams(window.location.search);
const jobUuid = urlParams.get('job_uuid');
const timestamp = urlParams.get('ts');
let iframeSrc = "";
if (jobUuid && timestamp) {
// Wenn beide Parameter vorhanden sind, den spezifischen Buchungslink verwenden
iframeSrc = "https://floke-ai.duckdns.org/feedback/book_slot/" + jobUuid + "/" + timestamp;
} else {
// Andernfalls auf die allgemeine MS Bookings URL verweisen
iframeSrc = "https://outlook.office.com/book/KennenlernenmitRoboplanet@wackler-group.de/?ismsaljsauthenabled";
}
document.getElementById('booking-iframe').src = iframeSrc;
</script>
```
**2. Nginx-Konfiguration für korrekte Pfad-Weiterleitung**
Das Laden der spezifischen `/feedback/book_slot/...`-URL im iFrame scheiterte initial, da der Nginx-Proxy die verschachtelten Pfade nicht korrekt an den `lead-engine`-Container weiterleitete. Dies führte zur "Synology-Fehlerseite".
* **Problem:** Die `location`-Regel in `nginx-proxy-clean.conf` für `/feedback/` war zu starr.
* **Lösung:** Die Konfiguration wurde mit einer `rewrite`-Regel angepasst, um sicherzustellen, dass der gesamte Pfad korrekt an den `lead-engine`-Dienst weitergegeben wird und die notwendigen Proxy-Header für moderne Webanwendungen gesetzt sind.
```nginx
location /feedback/ {
auth_basic off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
rewrite ^/feedback/(.*)$ /$1 break;
proxy_pass http://lead-engine:8004;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
* **Aktion nach Änderung:** Nach dem Anpassen von `nginx-proxy-clean.conf` muss der Nginx-Container neu gestartet werden:
`docker-compose restart nginx`
**3. Behebung des `lead-engine` Service-Absturzes (`SyntaxError`)**
Der `lead-engine`-Dienst selbst stürzte aufgrund eines Python `SyntaxError` ab, der durch einen ungültigen Import-Pfad verursacht wurde, der Bindestriche enthielt. Dies verhinderte, dass der Dienst überhaupt eine Antwort senden konnte, was ebenfalls zu einer leeren iFrame-Anzeige beitrug.
* **Problem:** Eine Zeile `from connector-superoffice.superoffice_client import SuperOfficeClient` in `manager.py` verursachte einen `SyntaxError`, da Bindestriche in Python-Modulnamen nicht erlaubt sind.
* **Lösung:** Die fehlerhafte `sys.path.append`-Anweisung und die ungültige `import`-Zeile wurden aus `lead-engine/trading_twins/manager.py` entfernt, da sie Teil eines verschobenen Tasks waren.
* **Aktion nach Änderung:** Nach der Korrektur in `manager.py` muss der `lead-engine`-Container neu gebaut und neu gestartet werden:
`docker-compose up -d --build --force-recreate lead-engine`
Durch diese Maßnahmen wird die iFrame-Integration nun robust funktionieren und immer eine sinnvolle Ansicht auf der WordPress-Landingpage bieten.
## 🚀 Inbetriebnahme & Test ## 🚀 Inbetriebnahme & Test
### Inbetriebnahme ### Inbetriebnahme

View File

@@ -10,7 +10,7 @@ from zoneinfo import ZoneInfo
from threading import Thread, Lock from threading import Thread, Lock
import uvicorn import uvicorn
import logging import logging
from fastapi import FastAPI, Response, BackgroundTasks from fastapi import FastAPI, Request, Response, BackgroundTasks
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
@@ -206,6 +206,50 @@ def create_calendar_invite(lead_email, company, start_time):
# --- FastAPI Server --- # --- FastAPI Server ---
app = FastAPI() app = FastAPI()
# --- Webhook Endpoints for Smartlead ---
SMARTLEAD_LOG_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "Log")
SMARTLEAD_LOG_FILE = os.path.join(SMARTLEAD_LOG_DIR, "smartlead_webhooks.log")
# Ensure log directory exists
os.makedirs(SMARTLEAD_LOG_DIR, exist_ok=True)
async def log_webhook_data(webhook_type: str, request: Request):
"""Helper function to log incoming webhook data."""
try:
data = await request.json()
timestamp = datetime.now(TZ_BERLIN).isoformat()
log_entry = {
"timestamp": timestamp,
"webhook_type": webhook_type,
"source_ip": request.client.host,
"payload": data
}
with open(SMARTLEAD_LOG_FILE, "a", encoding="utf-8") as f:
f.write(json.dumps(log_entry) + "\n")
logging.info(f"Successfully processed and logged '{webhook_type}' webhook.")
return {"status": "success", "message": f"{webhook_type} webhook received"}
except json.JSONDecodeError:
logging.error("Webhook received with invalid JSON format.")
return Response(content='{"status": "error", "message": "Invalid JSON format"}', status_code=400, media_type="application/json")
except Exception as e:
logging.error(f"Error processing '{webhook_type}' webhook: {e}")
return Response(content='{"status": "error", "message": "Internal server error"}', status_code=500, media_type="application/json")
@app.post("/webhook/hot-lead", status_code=202)
async def webhook_hot_lead(request: Request):
"""Webhook endpoint for 'hot leads' from Smartlead."""
return await log_webhook_data("hot-lead", request)
@app.post("/webhook/follow-up-lead", status_code=202)
async def webhook_follow_up_lead(request: Request):
"""Webhook endpoint for 'follow-up leads' from Smartlead."""
return await log_webhook_data("follow-up-lead", request)
# --- END Webhook Endpoints ---
@app.get("/test_lead", status_code=202) @app.get("/test_lead", status_code=202)
def trigger_test_lead(background_tasks: BackgroundTasks): def trigger_test_lead(background_tasks: BackgroundTasks):
req_id = f"test_{int(time.time())}" req_id = f"test_{int(time.time())}"

View File

@@ -121,10 +121,27 @@ http {
proxy_set_header Connection "upgrade"; proxy_set_header Connection "upgrade";
} }
# Feedback API (public)
location /feedback/ { location /feedback/ {
auth_basic off; auth_basic off;
proxy_pass http://lead-engine:8004/; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
rewrite ^/feedback/(.*)$ /$1 break;
proxy_pass http://lead-engine:8004;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Smartlead Webhooks (public)
location /public/smartlead/ {
auth_basic off;
# Rewrite the URL to remove the public prefix and pass the rest to the webhook handler
# e.g., /public/smartlead/hot-lead -> /webhook/hot-lead
rewrite ^/public/smartlead/(.*)$ /webhook/$1 break;
proxy_pass http://lead-engine:8004;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;