Files
BSN-Livesystem/docs/alicia-aufgaben-nacht2.md

271 lines
7.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Alicia — Aufgaben 610: BSN-Livesystem
> **Repo:** `git@git.datenhimmel.work:danielkrause/BSN-Livesystem.git`
> **Branch:** `main`
> **Deine Basis:** Aufgaben 15 sind fertig und gepusht.
> **Qualität:** Vor JEDEM Commit: `ruff check . && ruff format . && mypy --config-file pyproject.toml . && pytest -v`
> **Fehler?** Code korrigieren, Checks wiederholen. Nicht aufgeben bis ALLE grün.
---
## Aufgabe 6 — Webhook-Signatur-Prüfung
### Datei: `src/queue/webhook.py`
```python
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:
1. Korrekte WhatsApp-Signatur → True
2. Falsche WhatsApp-Signatur → False
3. Fehlender App-Secret → False
4. Korrekter Telegram-Token → True (setze `TELEGRAM_BOT_TOKEN` mit ≥21 Zeichen)
5. Zu kurzer Telegram-Token → False
**Commit:** `feat: Webhook-Signatur-Prüfung für WhatsApp + Telegram`
---
## Aufgabe 7 — Queue-Worker
### Datei: `src/queue/worker.py`
```python
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`):
1. `verarbeite_submission` setzt Status auf "erledigt"
2. `worker_loop` verarbeitet alle Submissions in der Queue (nach Batch prüfen)
3. `worker_loop` crashed nicht bei leerer Queue
**Commit:** `feat: Queue-Worker mit Batch-Verarbeitung und Fehlertoleranz`
---
## Aufgabe 8 — Flask-App-Factory
### Datei: `src/app.py`
```python
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()`):
1. `create_app()` gibt Flask-App zurück
2. GET `/health` → 200, JSON mit `"status": "ok"`
3. GET `/health``queue_size` ist 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"` und `hub.verify_token == "bsn_verify_token"` → gibt `hub.challenge` zurü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_SECRET` aus `os.environ`
### Test-Datei: `tests/test_whatsapp.py`
4 Tests mit `app.test_client()` (Blueprint temporär registrieren):
1. GET mit korrektem verify_token → 200 + Challenge
2. GET mit falschem verify_token → 403
3. POST mit korrekter Signatur → 200
4. 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 → 400
- `message_id`, `chat_id`, `text` aus `data["message"]` extrahieren
- Fehlende `message_id` oder `text` → 400
- `Submission` bauen (`plattform="telegram"`) → `current_app.queue_handler.enqueue()` → 200
- Pydantic `ValidationError` abfangen → 400
### Test-Datei: `tests/test_telegram.py`
4 Tests mit `app.test_client()`:
1. POST mit gültigem Payload → 200
2. POST ohne JSON → 400
3. POST ohne `message_id` → 400
4. 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`