Admin
Betriebshandbuch
Interne Referenz für Preismodell, Permissions, Slide-QA und Deploy-Pipeline. Kein Kundendokument — nur für den Admin-Bereich.
Pakete & Preise
Modell B — Access-Pass
User kaufen keinen Einzelkurs, sondern einen zeitlich unbegrenzten Zugangs-Pass (Paket). Das Paket enthält eine definierte Liste von Schulungen plus optionale Permission-Bits. Preisänderungen wirken immer nur für Neukäufe — bestehende Paketzuweisungen bleiben unverändert (Grandfathering).
Preisfelder
Streichpreis (compare_at_price)
Optionaler Vergleichspreis, der durchgestrichen neben dem echten Preis angezeigt wird. Nur sichtbar wenn compare_at_price > price. Gilt ausschließlich für Neukäufe — bestehende Paketzuweisungen werden nicht berührt (Grandfathering).
Streichpreis (compare_at_price)
Optionaler Vergleichspreis, der durchgestrichen neben dem echten Preis angezeigt wird. Nur sichtbar wenn compare_at_price > price. Gilt ausschließlich für Neukäufe — bestehende Paketzuweisungen werden nicht berührt (Grandfathering).
- price_eur
- Tatsächlicher Verkaufspreis. Pflichtfeld wenn das Paket verkauft werden soll.
- compare_at_price
- Streichpreis. Nur angezeigt wenn größer als price_eur.
- badge_label
- Text auf dem Highlight-Badge, z.B. „Beliebt" oder „Angebot".
- is_highlighted
- Hebt das Paket auf der Landingpage hervor (Badge + prominente Platzierung).
Sonderflags
All-Access-Flag
Ein Paket mit access_all = 1 gewährt automatisch Zugriff auf ALLE neutralen Schulungen — auch solche, die nach dem Kauf neu hinzukommen. PST-Varianten sind immer ausgenommen. Nützlich für unbegrenzte Mitgliedschafts-Pakete.
All-Access-Flag
Ein Paket mit access_all = 1 gewährt automatisch Zugriff auf ALLE neutralen Schulungen — auch solche, die nach dem Kauf neu hinzukommen. PST-Varianten sind immer ausgenommen. Nützlich für unbegrenzte Mitgliedschafts-Pakete.
- access_all
- Gewährt Zugriff auf alle neutralen Schulungen, auch zukünftige. PST-Varianten immer ausgenommen. Nur für Premium/Unbegrenzt-Pakete setzen.
- is_free / Köder
- Schulung ohne Paket für alle eingeloggten User frei. Kein Package-Eintrag nötig.
Gratis-Köder (is_free)
Schulungen mit is_free = 1 sind für jeden eingeloggten User ohne Paket oder Zahlung sofort zugänglich. Kein Package-Eintrag nötig. Taktik: niedrige Einstiegshürde, um Leute auf die Plattform zu bringen. Vorsicht: Änderungen wirken sofort für alle bestehenden Accounts.
Permission-System
Auflösungs-Priorität
Die erste Regel, die greift, gewinnt. Weiter unten liegende Regeln werden nicht mehr geprüft.
- 1. user_overrides Direkt-Permission auf User ↔ Schulung. Absoluter Vorrang, auch wenn das Paket nichts enthält.
- 2. courses.is_free Schulung ist für alle eingeloggten User frei — kein Paket nötig.
- 3. access_all-Paket User hat ein Paket mit access_all = 1 → alle neutralen Schulungen freigegeben.
- 4. package_courses Schulung ist im Paket des Users enthalten (und Paket ist dem User zugewiesen).
Permission-Bits
View / Handout / PDF
Drei unabhängige Permission-Bits pro Schulung innerhalb eines Pakets:
• View — Schulung anschauen (Slides + Player)
• Handout — HTML-Skript herunterladen
• PDF — ZIP-Archiv mit Vorlagen-Dateien
View ist Voraussetzung für Handout und PDF. Alle drei können auf Paket-Ebene oder als Direkt-Permission gesetzt werden.
View / Handout / PDF
Drei unabhängige Permission-Bits pro Schulung innerhalb eines Pakets: • View — Schulung anschauen (Slides + Player) • Handout — HTML-Skript herunterladen • PDF — ZIP-Archiv mit Vorlagen-Dateien View ist Voraussetzung für Handout und PDF. Alle drei können auf Paket-Ebene oder als Direkt-Permission gesetzt werden.
Schulung anschauen — Slides + Player sichtbar. Voraussetzung für alle anderen Bits.
HTML-Skript-Download. Erfordert view = 1.
ZIP-Archiv mit Vorlagen. Erfordert view = 1. Im UI oft als "ZIP" beschriftet.
Bits lassen sich sowohl auf Paket-Ebene (gilt für alle User des Pakets) als auch per Direkt-Permission (gilt für einen einzelnen User) setzen.
Slide-Q&A
Approval-Status
Approval: pending_review
Slide-Feedback von regulären Usern landet zunächst in pending_review und wird niemandem außer dem Admin angezeigt. Erst nach manueller Freigabe (approve) wird es für andere User sichtbar. Admin-eigene Einträge bekommen automatisch den Status "auto" und brauchen keine Freigabe.
Standard-Workflow
- 1 User markiert Feedback auf einer Folie → landet als pending_review in der DB.
- 2 Admin öffnet Slide-Q&A, sieht alle pending_review-Einträge.
- 3 Admin ergänzt optional eine Notiz und exportiert als Markdown.
- 4 Markdown wird in Claude Code eingefügt → alle betroffenen Folien werden angepasst.
- 5 Nach dem Update: Bulk-Resolve-Button nutzen oder einzeln auf resolved setzen.
- 6 Resolved-Einträge verschwinden aus der Standard-Ansicht (Filter „Alle" zeigt sie noch).
Token-Action: resolve-ids
Maschinenlesbare Endpoint-Action, die Claude Code nach einem Bulk-Fix aufrufen kann, um eine Liste von Feedback-IDs auf resolved zu setzen — ohne manuellen Admin-Klick.
POST /api/admin/slide-qa.php?action=resolve-ids
Headers: X-CSRF-Token: <token>
Body: { "ids": [42, 43, 44], "token": "$APP_SECRET" }
APP_SECRET liegt im Secret-Store unter pst/erfolg-durch-ki/app-secret — niemals in den Code.
Deploy-Pipeline & Fallen
Pipeline-Übersicht
Bekannte Fallen
Nach einem Deploy mit neuen DB-Migrationen muss manuell aufgerufen werden:
/api/admin/migrate.php?token=$APP_SECRET
Erst aufrufen wenn die CI-Queue komplett leer ist —
sonst läuft die Migration gegen den alten Stand.
Der Migrator stoppt bei der ersten fehlerhaften Migration und rollt nicht zurück.
Bilder in Schulungs-HTML-Dateien können nicht als relative Pfade eingebunden werden —
serve.php reicht sie nicht durch. Lösung: Bilder als
base64-inline Data-URL im HTML einbetten.
Das passiert automatisch wenn du mit build.py all baust.
Zwei schnell aufeinanderfolgende Pushes queuen sich. Der zweite startet erst nach Abschluss des ersten. Plan: warte mit Migrationen, bis du weißt dass kein weiterer Push in den nächsten 25 Minuten kommt.
Migration — Kurzanleitung
- 1 git push → warten bis CI-Queue leer (GitHub Actions → alle grünen Häkchen).
- 2 Migration-Endpoint aufrufen: /api/admin/migrate.php?token=$APP_SECRET
- 3 Response prüfen: {"status":"ok","applied":N} oder {"error":"...","failed_at":"..."}
- 4 Bei Fehler: Logs im Backend prüfen, Migration manuell fixen, erneut pushen + warten + aufrufen.