7.8 KiB
Alicia — Aufgaben 6–10: BSN-Livesystem
Repo:
git@git.datenhimmel.work:danielkrause/BSN-Livesystem.gitBranch:mainDeine Basis: Aufgaben 1–5 sind fertig und gepusht. Qualität: Vor JEDEM Commit:ruff check . && ruff format . && mypy --config-file pyproject.toml . && pytest -vFehler? Code korrigieren, Checks wiederholen. Nicht aufgeben bis ALLE grün.
Aufgabe 6 — Webhook-Signatur-Prüfung
Datei: src/queue/webhook.py
import hashlib
import hmac
import os
from src.queue.handler import QueueHandler
from src.models.submission import Submission
def verify_whatsapp_signature(
payload: bytes, signature: str, app_secret: str | None = None
) -> bool:
"""Verifiziert die WhatsApp X-Hub-Signature-256.
Args:
payload: Der rohe Request-Body.
signature: Der X-Hub-Signature-256 Header-Wert.
app_secret: Der WhatsApp App-Secret (aus Env wenn None).
Returns:
True wenn Signatur gültig.
"""
secret = app_secret or os.environ.get("WHATSAPP_APP_SECRET", "")
if not secret:
return False
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
def verify_telegram_signature(token: str | None = None) -> bool:
"""Verifiziert den Telegram Bot-Token.
Args:
token: Bot-Token (aus Env wenn None).
Returns:
True wenn Token gültig.
"""
expected = token or os.environ.get("TELEGRAM_BOT_TOKEN", "")
if not expected:
return False
return len(expected) > 20
def validate_and_enqueue(
payload: bytes, signature: str, plattform: str, qh: QueueHandler
) -> bool:
"""Validiert plattformspezifische Signatur und legt Submission in Queue.
Args:
payload: Der rohe Request-Body.
signature: Signatur-Header.
plattform: "whatsapp" oder "telegram".
qh: QueueHandler-Instanz.
Returns:
True wenn validiert und erfolgreich enqueued.
"""
if plattform == "whatsapp":
if not verify_whatsapp_signature(payload, signature):
return False
elif plattform == "telegram":
if not verify_telegram_signature():
return False
else:
return False
import json
data = json.loads(payload)
submission = Submission(
message_id=str(data.get("message_id", "")),
plattform=plattform,
absender=str(data.get("absender", "")),
text=str(data.get("text", "")),
)
return qh.enqueue(submission) is not None
Test-Datei: tests/test_webhook.py
5 Tests:
- Korrekte WhatsApp-Signatur → True
- Falsche WhatsApp-Signatur → False
- Fehlender App-Secret → False
- Korrekter Telegram-Token → True (setze
TELEGRAM_BOT_TOKENmit ≥21 Zeichen) - Zu kurzer Telegram-Token → False
Commit: feat: Webhook-Signatur-Prüfung für WhatsApp + Telegram
Aufgabe 7 — Queue-Worker
Datei: src/queue/worker.py
import time
from src.queue.handler import QueueHandler
from src.models.submission import Submission
def verarbeite_submission(submission: Submission) -> bool:
"""Verarbeitet eine einzelne Submission (Platzhalter für KI-Pipeline).
Args:
submission: Die zu verarbeitende Submission.
Returns:
Immer True im Erfolgsfall.
"""
submission.status = "erledigt"
return True
def worker_loop(
qh: QueueHandler, poll_interval: int = 2, batch_size: int = 5
) -> None:
"""Endlos-Schleife: Pollt Queue, verarbeitet Batches.
Args:
qh: QueueHandler-Instanz.
poll_interval: Sekunden zwischen Polls wenn Queue leer.
batch_size: Maximale Submissions pro Batch.
"""
while True:
submissions = qh.dequeue(batch_size)
if not submissions:
time.sleep(poll_interval)
continue
for submission in submissions:
try:
if verarbeite_submission(submission):
qh.mark_done(submission)
except Exception:
pass # Fehler in einer Submission → weitermachen
Test-Datei: tests/test_worker.py
3 Tests mit fakeredis (MOCKE time.sleep mit unittest.mock.patch):
verarbeite_submissionsetzt Status auf "erledigt"worker_loopverarbeitet alle Submissions in der Queue (nach Batch prüfen)worker_loopcrashed nicht bei leerer Queue
Commit: feat: Queue-Worker mit Batch-Verarbeitung und Fehlertoleranz
Aufgabe 8 — Flask-App-Factory
Datei: src/app.py
from flask import Flask
from src.queue.handler import QueueHandler
def create_app() -> Flask:
"""Erstellt und konfiguriert die Flask-App.
Returns:
Flask-App-Instanz mit QueueHandler.
"""
app = Flask(__name__)
app.config.from_mapping(
REDIS_HOST="localhost",
REDIS_PORT=6379,
)
app.queue_handler = QueueHandler(
redis_host=app.config["REDIS_HOST"],
redis_port=app.config["REDIS_PORT"],
)
@app.route("/health")
def health() -> dict[str, object]:
return {
"status": "ok",
"queue_size": app.queue_handler.queue_size(),
}
return app
if __name__ == "__main__":
app = create_app()
app.run(host="0.0.0.0", port=5002, debug=False)
Test-Datei: tests/test_app.py
3 Tests mit Flask-Test-Client (app.test_client()):
create_app()gibt Flask-App zurück- GET
/health→ 200, JSON mit"status": "ok" - GET
/health→queue_sizeist Integer
Commit: feat: Flask-App-Factory mit Health-Check und QueueHandler
Aufgabe 9 — WhatsApp-Webhook (Flask-Blueprint)
Datei 1: src/routes/__init__.py
Leer lassen.
Datei 2: src/routes/whatsapp.py
Blueprint "whatsapp" mit /webhook/whatsapp (GET + POST):
- GET: Hub-Challenge — prüft
hub.mode == "subscribe"undhub.verify_token == "bsn_verify_token"→ gibthub.challengezurück (200), sonst 403 - POST: Signatur via
verify_whatsapp_signature()prüfen → Submission bauen →current_app.queue_handler.enqueue()→ 200 - Ungültige Signatur → 401
WHATSAPP_APP_SECRETausos.environ
Test-Datei: tests/test_whatsapp.py
4 Tests mit app.test_client() (Blueprint temporär registrieren):
- GET mit korrektem verify_token → 200 + Challenge
- GET mit falschem verify_token → 403
- POST mit korrekter Signatur → 200
- POST mit falscher Signatur → 401
Setze os.environ["WHATSAPP_APP_SECRET"] = "test" und os.environ["WHATSAPP_VERIFY_TOKEN"] = "test-token" im Test-Setup.
Commit: feat: WhatsApp-Webhook mit Signatur-Prüfung und Queue-Enqueue
Aufgabe 10 — Telegram-Webhook (Flask-Blueprint)
Datei: src/routes/telegram.py
Blueprint "telegram" mit /webhook/telegram (NUR POST):
request.get_json(silent=True)mit None-Check → 400message_id,chat_id,textausdata["message"]extrahieren- Fehlende
message_idodertext→ 400 Submissionbauen (plattform="telegram") →current_app.queue_handler.enqueue()→ 200- Pydantic
ValidationErrorabfangen → 400
Test-Datei: tests/test_telegram.py
4 Tests mit app.test_client():
- POST mit gültigem Payload → 200
- POST ohne JSON → 400
- POST ohne
message_id→ 400 - POST mit leerem
text→ 400
Commit: feat: Telegram-Webhook mit JSON-Validierung und Queue-Enqueue
⚠️ WICHTIG
- Vor JEDEM Commit:
ruff check . && ruff format . && mypy --config-file pyproject.toml . && pytest -v - Nicht pushen bevor ALLE 4 Checks grün sind!
- Nach jeder Aufgabe: Commit + Push, dann nächste.
- Falls ein Test fehlschlägt: Code korrigieren, nicht aufgeben.
- Repo: Arbeite in
/tmp/BSN-Livesystem(oder wo du geklont hast) - Täglich vor Start:
git pull origin main