Plugin neuheiten: BGG-Sync mit APScheduler, Filtern und deutscher UI

- Datenmodell + eigene Migration 0001_neuheiten_tabelle (Tabelle neuheiten:
  Titel, Verlag, Autor, Erscheinungsjahr, BGG-ID, Status 'neuheit', Quelle,
  Zeitstempel; bgg_id eindeutig als Merge-Kriterium)
- BoardGameGeek XML API2-Client (search + thing, Batches à 20 IDs):
  Rate-Limit >= 1 s zwischen Requests, Retry mit exponentiellem Backoff bei
  5xx/429/Netzwerkfehlern, HTTP 202 gemäß Retry-After, robustes XML-Parsing;
  Transport/Uhr/Sleep injizierbar (keine echten Calls in Tests)
- Filter: Erweiterungen (boardgameexpansion) auf Request- und Elementebene
  ausgeschlossen; Prototypen per Titel-Heuristik (BGG hat keinen Marker)
- Sync-Service mit Update-statt-Duplikat-Logik über die eindeutige BGG-ID
  (Status bleibt erhalten); Fehler je Suchbegriff brechen den Lauf nicht ab
- APScheduler-Hintergrundjob (Standard 24 h) mit Überlappungsschutz,
  abschaltbar/intervallkonfigurierbar per Env; manueller
  'Jetzt synchronisieren'-Endpunkt nur für Admin/Redakteur, optional mit
  Sofort-Suchbegriff
- UI /neuheiten: sortier-/filterbare Tabelle mit Volltextsuche (HTMX-Teilladung,
  noscript-fähig), deutsche Oberfläche, BGG-Links, Ergebnis-Banner
- Plugin-Loader: idempotentes Laden (Modul-Caching), damit mehrere
  create_app()-Aufrufe dieselben Plugin-Klassen/Tabellen nutzen
- Tests: Parsing, Erweiterungs-/Prototyp-Filter, Rate-Limit/Backoff/202,
  Update-statt-Duplikat, Rollen am Sync-Endpunkt, Scheduler-Lifecycle —
  ausschließlich mit gemockten BGG-Antworten (uv run pytest: 96 grün)
- README/AGENTS: Plugin-Doku, Env-Variablen, Fortschrittstabelle aktualisiert
This commit is contained in:
ox-alpha
2026-08-21 19:10:43 +00:00
parent 83d574d95c
commit 56cc5943b1
16 changed files with 1732 additions and 28 deletions

View File

@@ -2,8 +2,8 @@
**KI-Assistenz für Spielemagazin-Redaktionen** — Multi-User-Webanwendung mit
modularer Plugin-Architektur. Lauffähiger Kern mit Plugin-System,
Authentifizierung/Rollen und Migrationen; als erstes Fachplugin ist das
**Audit-Log** vollständig implementiert, weitere Plugins folgen.
Authentifizierung/Rollen und Migrationen; vollständig implementiert sind
bisher **Audit-Log**, **Benachrichtigung** und **Neuheiten (BGG-Sync)**.
## Stack
@@ -12,6 +12,7 @@ Authentifizierung/Rollen und Migrationen; als erstes Fachplugin ist das
- **Auth:** Session-Cookies (signiert), Passwort-Hashing mit Argon2id
- **Plugins:** Entry-Points (`importlib.metadata`, Gruppe `spiele_redaktion.plugins`)
plus lokales `plugins/`-Verzeichnis
- **Hintergrund-Jobs:** APScheduler (BGG-Neuheiten-Sync)
- **Dependencies/Tests:** [uv](https://docs.astral.sh/uv/) + pytest
## Setup
@@ -47,6 +48,10 @@ Beim ersten Start wird automatisch ein Admin-Konto angelegt:
| `SPIELE_SMTP_ABSENDER` | Benutzer bzw. `spiele-redaktion@localhost` | From-Adresse |
| `SPIELE_SMTP_TLS` | `starttls` | `starttls`, `ssl` oder `keine` |
| `SPIELE_TELEGRAM_BOT_TOKEN` | *(leer)* | Bot-Token für den Telegram-Kanal; leer = Dev-Fallback (nur Protokoll) |
| `SPIELE_BGG_SYNC_AKTIV` | `1` | Hintergrund-Sync des Neuheiten-Plugins an (`1`) oder aus (`0`) |
| `SPIELE_BGG_SYNC_INTERVALL_STUNDEN` | `24` | Intervall des BGG-Syncs in Stunden (min. 1) |
| `SPIELE_BGG_SUCHBEGRIFFE` | `brettspiel` | Komma-getrennte Suchbegriffe für den regelmäßigen Sync |
| `SPIELE_BGG_MAX_TREFFER_PRO_SUCHE` | `25` | Obergrenze Treffer je Suchbegriff (schont das BGG-Rate-Limit) |
## Tests
@@ -59,7 +64,10 @@ Abgedeckt: Plugin-Loader lädt alle Plugins (inkl. Lifecycle-Hooks und
Entry-Point-Pfad), Migrations-Laufzeit inkl. Idempotenz, Login/Logout,
Rollen-Zugriff (admin/redakteur vs. rezensent), Benutzerverwaltung sowie das
Audit-Log-Plugin (Logging-Funktion, Migration, Filter, Paginierung,
Nur-Admin-Zugriff).
Nur-Admin-Zugriff) sowie das Neuheiten-Plugin: BGG-Parsing, Erweiterungs-/
Prototyp-Filter, Rate-Limit & Retry-Backoff (gemockte HTTP-Antworten, keine
echten API-Calls), Update-statt-Duplikat-Logik, UI-Suche/Filter/Sortierung,
Rollen am Sync-Endpunkt und Scheduler-Lifecycle.
## Architektur
@@ -75,7 +83,8 @@ src/redaktionskern/ schlanker Kern — KEINE Fachlogik
plugins/ ein Ordner pro Funktion, ladbar über den Plugin-Loader
├── audit-log/ VOLL IMPLEMENTIERT: Model, Migration, API, Admin-Ansicht
├── neuheiten/ benachrichtigung/ (in Arbeit)
├── neuheiten/ VOLL IMPLEMENTIERT: BGG-Client, Sync-Job, Migration, UI
├── benachrichtigung/ VOLL IMPLEMENTIERT: E-Mail/Telegram/In-App
├── dedup/ planung/ archiv/ erinnerung/ export/ (Stubs, ladbar)
└── … je __init__.py + templates/<name>/
```
@@ -196,6 +205,70 @@ bericht = await plugin.send_notification(user, "Titel", "Nachrichtentext", "kate
einzelne Kanalausfälle werden protokolliert und im Bericht gemeldet. Die
In-App-Nachricht landet im Posteingang des Benutzers.
## Plugin „neuheiten“ (implementiert)
Neuheitenliste auf Basis der **BoardGameGeek XML API2**
(`https://boardgamegeek.com/xmlapi2`). Regelmäßiger Sync als Hintergrund-Job,
manuelle Synchronisation für die Redaktion, deutsche sortier-/filterbare Liste.
### Datentabelle
Eigene Migration `0001_neuheiten_tabelle`, Tabelle `neuheiten`
(SQLite ↔ Postgres portabel):
| Spalte | Inhalt |
|--------|--------|
| `titel` | Spieltitel |
| `verlag` / `autor` | Verlag bzw. Autor(en), kommagetrennt |
| `erscheinungsjahr` | Erscheinungsjahr — BGG liefert nur das Jahr, kein genaues Datum |
| `bgg_id` | BoardGameGeek-ID, eindeutig → Merge-Kriterium des Syncs |
| `status` | `neuheit` (spätere Plugins: Planung/Archiv) |
| `quelle` | Datenquelle (`boardgamegeek`) |
| `erstellt_am` / `aktualisiert_am` | Zeitstempel |
### BGG-Client & Sync-Ablauf
Pro Suchbegriff: `/search?type=boardgame&query=…` → IDs sammeln →
`/thing?id=…&type=boardgame` in Batches à 20 IDs.
- **Rate-Limit:** mindestens 1 Sekunde zwischen zwei Requests.
- **Retry mit Backoff:** exponentielles Zurückhalten bei 5xx, 429 und
Netzwerkfehlern; HTTP 202 (BGG-Warteschlange) wird gemäß `Retry-After`
erneut versucht; kaputtes XML erzeugt eine klare Fehlermeldung.
- **Filter:**
- Erweiterungen (`type=boardgameexpansion`) werden auf Request-Ebene
(`type=boardgame`) und zusätzlich pro Element ausgeschlossen.
- Prototypen: Die XML API2 liefert keinen Prototyp-Marker; als Heuristik
werden Titel mit Prototyp-Schlüsselwörtern („Prototyp“, „Prototype“, …)
gefiltert. Restrisiko verbleibt — redaktionelle Prüfung bleibt wichtig.
- **Update statt Duplikat:** bestehende Einträge werden über die eindeutige
`bgg_id` aktualisiert (Titel/Verlag/Autor/Jahr), der `status` bleibt
erhalten; neu angelegt wird nur bei unbekannter BGG-ID.
### Hintergrund-Job & manuelle Synchronisation
Beim Start registriert das Plugin einen APScheduler-Job (Standard: alle 24 h,
konfigurierbar über `SPIELE_BGG_SYNC_INTERVALL_STUNDEN`, abschaltbar über
`SPIELE_BGG_SYNC_AKTIV=0`). Die Suchbegriffe kommen aus
`SPIELE_BGG_SUCHBEGRIFFE` (komma-getrennt). Läuft ein Sync noch, wird ein
überlappender Durchlauf übersprungen.
Unter **Neuheiten → „Jetzt synchronisieren“** lösen Admins und Redakteure
einen sofortigen Sync aus — optional mit einem Sofort-Suchbegriff für eine
gezielte Recherche. Das Ergebnis (neu/aktualisiert/gefiltert) erscheint als
Banner; der Endpunkt ist rollengeschützt (Rezensenten erhalten 403).
### UI
Die Seite `/neuheiten` zeigt alle Einträge als Tabelle (Spieltitel, Verlag,
Autor, Erscheinungsjahr, Status, aktualisiert am):
- Sortierung per Spaltenkopf, Suche über Titel/Verlag/Autor und Statusfilter —
serverseitig umgesetzt, per HTMX ohne Seitenreload (ohne JavaScript als
normaler Formular-GET nutzbar).
- Jeder Titel verlinkt direkt auf den BGG-Eintrag.
- Sync-Steuerung nur für Rolle Admin/Redakteur.
## Deployment (später)
Docker/Podman Compose ist vorgesehen (Henry-Lab, danach Kundenhardware).
@@ -211,7 +284,7 @@ geschrieben.
| 1 | Kern: FastAPI, Plugin-System, DB/Migrationen, Auth/Rollen, HTMX-Layout | ✅ fertig (28 Tests grün) |
| 2 | Plugin `benachrichtigung` (E-Mail/Telegram/In-App, User-Präferenzen) | 🔄 in Arbeit |
| 3 | Plugin `audit-log` (Wer/Was/Wann, Admin-Ansicht, öffentliche API) | ✅ fertig |
| 4 | Plugin `neuheiten` (BGG-Sync, APScheduler, Filter) | 🔄 in Arbeit |
| 4 | Plugin `neuheiten` (BGG-Sync, APScheduler, Filter) | ✅ fertig |
| 5 | Plugin `dedup` (Verlags-Konflikt, deutsche Version, Vorgänger-/Planungs-Check) | ⏳ offen |
| 6 | Plugin `planung` (Verschiebung, händischer Eintrag + Prüfungen + Benachrichtigung) | ⏳ offen |
| 7 | Plugin `archiv` (12-Monats-Autopilot) | ⏳ offen |
@@ -225,6 +298,6 @@ Legende: ✅ fertig · 🔄 in Arbeit · ⏳ offen · ⚠️ fertig mit offenen
## Offene Punkte (nicht Teil von Phase 1)
- CSRF-Schutz für Formulare (aktuell SameSite=Lax-Cookie als Basisschutz)
- Hintergrund-Jobs (APScheduler) für BGG-Sync und Erinnerungen
- Hintergrund-Jobs (APScheduler) für Erinnerungen
- WeasyPrint für PDF-Export (Systemabhängigkeiten im Container)
- Tailwind via CDN nur für Dev; für Produktion lokal gehostete Assets