Plugin archiv: 12-Monats-Autopilot mit Sicherungsdatei-Prinzip

- Eigene Migration 0001_archiv_tabellen: Spiegel-Tabellen archiv_neuheiten
  und archiv_planung (ohne FK, damit Archiv-Zeilen Benutzerlöschung überleben)
- Service mit eingefrierbarer Uhr: Stichtag „jetzt minus 12 Monate“ in UTC,
  strenger Vergleich (genau 12 Monate bleibt aktiv); Neuheiten nach
  Erscheinungsjahr (großzügig Jahresende) sonst Eintragsdatum, Planung nach
  Eintragsdatum; Monatsarithmetik mit Klemmung (29. Februar)
- Täglicher APScheduler-Cron-Job (Standard 03:00, konfigurierbar über
  SPIELE_ARCHIV_JOB_UHRZEIT / SPIELE_ARCHIV_JOB_AKTIV); je archiviertem
  Titel ein Audit-Log-Eintrag als System
- Admin-Ansicht /archiv: vereinigte Liste beider Tabellen, Suche über
  Titel/Verlag/Autor, Herkunftsfilter, Wiederherstellen in die aktive Liste
  (BGG-Konflikt wird abgelehnt, verwaiste Rezensentenzuordnung übernimmt der
  Admin — gemeldet und auditiert); Rollen: nur Admin
- Kern minimal erweitert: PluginContext.registry stellt Hintergrund-Jobs die
  Plugin-Registry bereit (keine Fachlogik im Kern)
- 30 neue Tests mit eingefrorener Uhr (Grenzfälle, Zeitzonen, Schaltjahr,
  Job-Lauf, Wiederherstellen, Rollen, Suche/Filter); 171 Tests grün
- README aktualisiert: neues Plugin-Kapitel, Env-Variablen, Fortschritt #7 
This commit is contained in:
Flo Hartmann
2026-08-21 20:26:00 +00:00
parent 0f12cd952b
commit 72fe3b7eda
10 changed files with 1727 additions and 25 deletions

View File

@@ -4,7 +4,7 @@
modularer Plugin-Architektur. Lauffähiger Kern mit Plugin-System,
Authentifizierung/Rollen und Migrationen; vollständig implementiert sind
bisher **Audit-Log**, **Benachrichtigung**, **Neuheiten (BGG-Sync)**,
**Dedup-Prüfung** und **Planungsliste**.
**Dedup-Prüfung**, **Planungsliste** und **Archiv (12-Monats-Autopilot)**.
## Stack
@@ -54,6 +54,8 @@ Beim ersten Start wird automatisch ein Admin-Konto angelegt:
| `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) |
| `SPIELE_DEDUP_BGG_AKTIV` | `1` | BGG-Zusatzdaten für die Dedup-Prüfung an (`1`) oder aus (`0`): Alternate-Names und Erweiterungs-Relationen |
| `SPIELE_ARCHIV_JOB_AKTIV` | `1` | Täglicher Archivierungs-Job an (`1`) oder aus (`0`) |
| `SPIELE_ARCHIV_JOB_UHRZEIT` | `03:00` | Tageszeit des täglichen Archiv-Laufs im Format `HH:MM` |
## Tests
@@ -75,6 +77,11 @@ Fake-BGG-Client, Prüfprotokoll, Prüfseite) und **planung** (beide Eintragswege
mit Stub-gemockter dedup-/Benachrichtigungs-/Audit-API, Verlags-Auswahl-
Dialog, Statuswechsel, Bearbeiten/Löschen, Rechte pro Rolle) plus ein
Integrationstest mit den echten Plugins (In-App-Nachricht + Audit-Eintrag).
Dazu das **archiv**-Plugin: 12-Monats-Regel mit eingefrorener Uhr (Grenzfälle
„genau 12 Monate“, Schaltjahr/29. Februar, UTC-Konsistenz bei
Zeitzonen-Unterschieden), Job-Lauf mit Audit-Einträgen, Wiederherstellen
inkl. BGG-Konflikt und Ersatz-Rezensent, Rollen sowie Suche/Filter in der
Admin-Ansicht.
## Architektur
@@ -94,7 +101,8 @@ plugins/ ein Ordner pro Funktion, ladbar über den Plugin-Load
├── benachrichtigung/ VOLL IMPLEMENTIERT: E-Mail/Telegram/In-App
├── dedup/ VOLL IMPLEMENTIERT: check_titel-API, Heuristik, Protokoll
├── planung/ VOLL IMPLEMENTIERT: Planungsliste, Verschiebung, Dialoge
├── archiv/ erinnerung/ export/ (Stubs, ladbar)
├── archiv/ VOLL IMPLEMENTIERT: 12-Monats-Autopilot, Job, UI
├── erinnerung/ export/ (Stubs, ladbar)
└── … je __init__.py + templates/<name>/
```
@@ -122,7 +130,9 @@ class MeinPlugin(BasePlugin):
return [NavEntry(label=self.title, url="/meinplugin")]
def on_load(self, context): # Lifecycle-Hook beim Start
super().on_load(context) # context.engine/.session_factory/.templates/.settings
super().on_load(context) # context.engine/.session_factory/.templates/
# .settings/.registry (Plugin-Registry für
# Hintergrund-Jobs ohne Request)
def on_unload(self): # Lifecycle-Hook beim Herunterfahren
super().on_unload()
@@ -363,6 +373,64 @@ Rezensenten können **nur ihre eigenen Einträge** bearbeiten (Statuswechsel,
Bearbeiten, Löschen); Admins und Redakteure alle. Jede Änderung wird im
Audit-Log protokolliert (`erstellt`/`verschoben`/`geaendert`/`geloescht`).
## Plugin „archiv“ (implementiert)
Automatische Archivierung nach dem **Sicherungsdatei-Prinzip**: Titel, deren
Erscheinungs- bzw. Eintragsdatum länger als 12 Monate zurückliegt, werden
vollständig in eigene Archiv-Tabellen verschoben und aus den aktiven Listen
entfernt. Nichts wird gelöscht oder verändert — jeder Eintrag lässt sich
unverändert zurück in die aktive Liste verschieben.
### Datentabellen
Eigene Migration `0001_archiv_tabellen` mit zwei Spiegel-Tabellen
(SQLite ↔ Postgres portabel, ohne Fremdschlüssel — archivierte Zeilen
überleben auch das Löschen eines Benutzers):
| Tabelle | Spiegel von | Besondere Spalten |
|---------|-------------|-------------------|
| `archiv_neuheiten` | `neuheiten` | alle Originalspalten plus `quell_id`, `archiviert_am`, `archiviert_von`, `grund`, `referenz_am` |
| `archiv_planung` | `planungsliste` | alle Originalspalten (inkl. `pruefung`-JSON) plus dieselben Archiv-Metadaten |
### Die 12-Monats-Regel
- **Stichtag:** „jetzt 12 Monate“ in UTC; archiviert wird, wer *streng
älter* ist — genau 12 Monate gilt noch nicht als „älter als“.
- **Referenzdatum:**
- Neuheiten: das Erscheinungsdatum. BGG liefert nur das Jahr, daher
großzügig das Jahresende (31.12., 23:59:59) — ein Titel von 2023 wird
erst ab dem 01.01.2025 archiviert. Ohne Jahr zählt das Eintragsdatum.
- Planungseinträge: das Eintragsdatum, unabhängig vom Status.
- **Zeitzonen:** verglichen wird durchgängig in UTC; naive Datenbank-
Zeitstempel gelten als UTC, zeitzonenbewusste Uhrzeiten werden nach UTC
verschoben (per Tests mit eingefrorener Uhr abgedeckt, inkl. 29. Februar).
### Täglicher Hintergrund-Job
Beim Start registriert das Plugin einen APScheduler-Cron-Job (Standard
täglich **03:00**, konfigurierbar über `SPIELE_ARCHIV_JOB_UHRZEIT`,
abschaltbar über `SPIELE_ARCHIV_JOB_AKTIV=0`). Pro archiviertem Titel
schreibt der Job einen **Audit-Log-Eintrag** mit dem Akteur „System“
(Aktion `verschoben`, Grund und Referenzdatum in den Details). Der
Registry-Zugriff aus dem Hintergrund-Job läuft über den neuen
`context.registry`-Hook des Plugin-Kontexts.
### Admin-Ansicht mit Wiederherstellen
Unter **Archiv** (`/archiv`, nur Rolle **Admin**) werden beide Tabellen als
vereinigte Liste angezeigt (neueste Archivierung zuerst):
- Suche über Titel/Verlag/Autor und Filter nach Herkunft (Neuheiten/Planung).
- Pro Eintrag: Herkunfts-Badge, Status, Rezensent, Archivierungszeitpunkt
und der Grund (z. B. „älter als 12 Monate (Erscheinungsjahr 2023)“).
- **Wiederherstellen** verschiebt den Eintrag vollständig zurück in die
aktive Liste und protokolliert dies im Audit-Log (Akteur: der Admin).
Konfliktfälle werden sicher abgelehnt: existiert in der Neuheitenliste
bereits ein aktiver Eintrag mit derselben BGG-ID (erneuter Sync), wird
nicht wiederhergestellt; existiert der zugeordnete Rezensent nicht mehr,
übernimmt der wiederherstellende Admin die Zuordnung (gemeldet und
auditiert). Rezensenten und Redakteure erhalten 403.
## Deployment (später)
Docker/Podman Compose ist vorgesehen (Henry-Lab, danach Kundenhardware).
@@ -379,9 +447,9 @@ geschrieben.
| 2 | Plugin `benachrichtigung` (E-Mail/Telegram/In-App, User-Präferenzen) | ✅ fertig |
| 3 | Plugin `audit-log` (Wer/Was/Wann, Admin-Ansicht, öffentliche API) | ✅ fertig |
| 4 | Plugin `neuheiten` (BGG-Sync, APScheduler, Filter) | ✅ fertig |
| 5 | Plugin `dedup` (Verlags-Konflikt, deutsche Version, Vorgänger-/Planungs-Check) | ✅ fertig (141 Tests grün) |
| 6 | Plugin `planung` (Verschiebung, händischer Eintrag + Prüfungen + Benachrichtigung) | ✅ fertig (141 Tests grün) |
| 7 | Plugin `archiv` (12-Monats-Autopilot) | ⏳ offen |
| 5 | Plugin `dedup` (Verlags-Konflikt, deutsche Version, Vorgänger-/Planungs-Check) | ✅ fertig |
| 6 | Plugin `planung` (Verschiebung, händischer Eintrag + Prüfungen + Benachrichtigung) | ✅ fertig |
| 7 | Plugin `archiv` (12-Monats-Autopilot, Wiederherstellen, täglicher Job) | ✅ fertig (171 Tests grün) |
| 8 | Plugin `erinnerung` (Redaktionsschluss pro Ausgabe, 4-Wochen-Erinnerung) | ⏳ offen |
| 9 | Plugin `export` (CSV + PDF via WeasyPrint) | ⏳ offen |
| 10 | Integrationstests über alle Plugins | ⏳ offen |