- 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
124 lines
4.9 KiB
Markdown
124 lines
4.9 KiB
Markdown
# 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](https://docs.astral.sh/uv/) + pytest
|
|
|
|
## Setup
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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`.
|
|
|
|
```python
|
|
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
|