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

37
uv.lock generated
View File

@@ -37,6 +37,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" },
]
[[package]]
name = "apscheduler"
version = "3.11.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "tzlocal" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8c/6b/eeff360196bb20b312c9e762a820fd1b2c6d809466c755ef57863478e454/apscheduler-3.11.3.tar.gz", hash = "sha256:cd2fcc9330039a81a5893472ad49facf23a6d5604cbe1d918c835c6de7834d5a", size = 110312, upload-time = "2026-06-28T19:39:22.493Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/42/c9/8638db32514dbb9157b3d82680c6faea89283523edf9ed2415ea3884f2ae/apscheduler-3.11.3-py3-none-any.whl", hash = "sha256:bbeb2ec02d23d3c06a6c07ed7f0f3939ada6680eb121fae809a69bb42c537a30", size = 66024, upload-time = "2026-06-28T19:39:20.982Z" },
]
[[package]]
name = "argon2-cffi"
version = "25.1.0"
@@ -743,8 +755,10 @@ name = "spiele-redaktion"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "apscheduler" },
{ name = "argon2-cffi" },
{ name = "fastapi" },
{ name = "httpx" },
{ name = "itsdangerous" },
{ name = "jinja2" },
{ name = "python-multipart" },
@@ -760,8 +774,10 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "apscheduler", specifier = ">=3.11,<4" },
{ name = "argon2-cffi", specifier = ">=23.1" },
{ name = "fastapi", specifier = ">=0.115" },
{ name = "httpx", specifier = ">=0.27" },
{ name = "itsdangerous", specifier = ">=2.1" },
{ name = "jinja2", specifier = ">=3.1" },
{ name = "python-multipart", specifier = ">=0.0.9" },
@@ -851,6 +867,27 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" },
]
[[package]]
name = "tzdata"
version = "2026.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" },
]
[[package]]
name = "tzlocal"
version = "5.4.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "tzdata", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/81/5b/879b2f932adfa7a053c360d50bc896c977fa6426109185f7c12ebdd0cb9d/tzlocal-5.4.4.tar.gz", hash = "sha256:8dbb8660838688a7b6ba4fed31d18dedf842afb4d47ca050d6d891c2c15f3be4", size = 31170, upload-time = "2026-06-29T08:03:40.026Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9e/a4/017a7a6cbe387d961a688ec31364ae60a5c4e22c96ae9921b79a947c855d/tzlocal-5.4.4-py3-none-any.whl", hash = "sha256:aae09f0126a8a86fa736be266eb4a471380d26a0de3bc14844e7821fee3e2a15", size = 18115, upload-time = "2026-06-29T08:03:38.666Z" },
]
[[package]]
name = "uvicorn"
version = "0.52.4"