Files
spiele-redaktion/README.md
Flo Hartmann 96a37e878b Phase 1: Kern mit Plugin-System, Auth/Rollen, Migrations, Plugin-Stubs
- redaktionskern (src/): schlanker Kern — App-Fabrik, Plugin-Loader
  (Entry-Points + plugins/-Verzeichnis), Migrations-Laufzeit
  (schema_migrations pro Plugin, SQLite-/Postgres-portabel),
  Auth mit Argon2id + Session-Cookies, Rollen admin/redakteur/rezensent,
  Benutzerverwaltung für Admins
- plugins/: 8 ladbare Stubs (neuheiten, dedup, planung, archiv,
  erinnerung, benachrichtigung, audit-log, export) nach Plugin-Vertrag
- Frontend: Jinja2 + Tailwind (CDN) + HTMX + Alpine.js, UI deutsch
- Tests: 28 pytest-Fälle (Loader, Lifecycle, Entry-Points, Migrations-
  Idempotenz, Login/Logout, Rollen-Zugriff, Benutzerverwaltung)
- Docker/Podman: Compose (Traefik-Labels) + Dockerfile (uv)
- README.md mit Setup-Anleitung
2026-08-21 18:35:09 +00:00

4.9 KiB

Spiele-Redaktion

KI-Assistenz für Spielemagazin-Redaktionen — Multi-User-Webanwendung mit modularer Plugin-Architektur. Phase 1: lauffähiger Kern mit Plugin-System, Authentifizierung/Rollen, Migrationen und Plugin-Stubs.

Stack

  • Backend: Python 3.11+, FastAPI, SQLAlchemy 2 (SQLite, Postgres-fähig)
  • Frontend: HTMX + Jinja2 + Tailwind (CDN) + Alpine.js — kein Build-Step
  • Auth: Session-Cookies (signiert), Passwort-Hashing mit Argon2id
  • Plugins: Entry-Points (importlib.metadata, Gruppe spiele_redaktion.plugins) plus lokales plugins/-Verzeichnis
  • Dependencies/Tests: uv + pytest

Setup

uv sync                                   # venv + Dependencies
uv run uvicorn main:app --reload          # Dev-Server auf http://127.0.0.1:8000

Beim ersten Start wird automatisch ein Admin-Konto angelegt:

Benutzername Passwort Rolle
admin admin admin

⚠️ Für alles außer der lokalen Entwicklung vor dem Start SPIELE_INITIAL_ADMIN_PASSWORD setzen und das Passwort nach dem ersten Login ändern.

Umgebungsvariablen

Variable Default Bedeutung
SPIELE_DATABASE_URL sqlite:///./data/spiele-redaktion.db SQLAlchemy-URL (Postgres: z. B. postgresql+psycopg://…)
SPIELE_SESSION_SECRET Dev-Wert Secret für signierte Session-Cookies
SPIELE_SESSION_DAUER 43200 Session-Dauer in Sekunden (12 h)
SPIELE_INITIAL_ADMIN_PASSWORD admin Passwort des initialen Admins
SPIELE_PLUGINS_DIR <Projekt>/plugins Pfad zum lokalen Plugin-Verzeichnis

Tests

uv run pytest

Abgedeckt: Plugin-Loader lädt alle Stubs (inkl. Lifecycle-Hooks und Entry-Point-Pfad), Migrations-Laufzeit inkl. Idempotenz, Login/Logout, Rollen-Zugriff (admin/redakteur vs. rezensent), Benutzerverwaltung.

Architektur

src/redaktionskern/        schlanker Kern — KEINE Fachlogik
├── app.py                 App-Fabrik: Discovery → Migrations → on_load → Routen
├── config.py              Einstellungen (Umgebungsvariablen)
├── db.py                  Engine/Session (SQLite ↔ Postgres portabel)
├── migrationen.py         Migrations-Laufzeit (Tabelle schema_migrations pro Plugin)
├── plugin_loader.py       Entry-Points + plugins/-Verzeichnis, eindeutige Namen
├── contracts.py           Plugin-Vertrag (BasePlugin, Migration, NavEntry, PluginContext)
└── auth/                  Login/Logout, Rollen, Benutzerverwaltung

plugins/                   ein Ordner pro Funktion (Phase-1: Stubs, ladbar)
├── neuheiten/  dedup/  planung/  archiv/
├── erinnerung/ benachrichtigung/ audit-log/ export/
└── …                      je __init__.py + templates/<name>/index.html

Der Plugin-Vertrag

Ein Plugin ist ein Paketordner unter plugins/ mit einem __init__.py, das ein Modulattribut plugin (Instanz einer BasePlugin-Unterklasse) bereitstellt. Alternativ: installiertes Paket mit Entry-Point in der Gruppe spiele_redaktion.plugins.

from fastapi import Depends, Request
from redaktionskern.auth.deps import require_user
from redaktionskern.contracts import BasePlugin, Migration, NavEntry

class MeinPlugin(BasePlugin):
    name = "meinplugin"          # eindeutig, = Ordnername
    title = "Mein Plugin"        # deutscher Anzeigename
    description = "Was es tut."

    def migrations(self):        # eigene Schema-Migrationen (optional)
        return []

    def navigation(self):        # Einträge in der Hauptnavigation (optional)
        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

    def on_unload(self):         # Lifecycle-Hook beim Herunterfahren
        super().on_unload()

plugin = MeinPlugin()

Der Kern stellt jedem Plugin über context.templates eine gemeinsame Jinja- Umgebung bereit (Plugin-Templates erben base.html), dazu Engine und Session-Fabrik. Migrationen laufen transaktional und werden pro Plugin in schema_migrations protokolliert; die up-Funktionen bekommen eine SQLAlchemy-Connection und können bei Dialekt-Unterschieden verzweigen.

Deployment (später)

Docker/Podman Compose ist vorgesehen (Henry-Lab, danach Kundenhardware). Für Postgres genügt SPIELE_DATABASE_URL; die Migrationen sind portabel geschrieben.

Offene Punkte (nicht Teil von Phase 1)

  • CSRF-Schutz für Formulare (aktuell SameSite=Lax-Cookie als Basisschutz)
  • Fachliche Plugins (Neuheiten/BGG-Sync, Dedup, Planung, Archiv, Erinnerung, Benachrichtigung mit Adapter-Muster, Audit-Log, CSV/PDF-Export)
  • Hintergrund-Jobs (APScheduler) für BGG-Sync und Erinnerungen
  • WeasyPrint für PDF-Export (Systemabhängigkeiten im Container)
  • Tailwind via CDN nur für Dev; für Produktion lokal gehostete Assets
  • Docker-/Podman-Compose-Setup