- Unterseite /admin/newsletter/abmeldungen listet Newsletter-Opt-outs mit
Zeitpunkt, Suche und Link zur User-Detailseite
- Kampagnen-Filter: Empfänger einer früheren Kampagne ausschließen
(Abgleich per User-ID und E-Mail-Snapshot) für Follow-up-Mailings
- Login-Filter kann Monats-Scheiben (von–bis) abgrenzen, um große
Listen Monat für Monat abzuarbeiten
- Versand läuft in Wellen (Standard 50 Mails / 15 Min, per .env
justierbar) gegen SMTP-Limits und Blocklisten; Hinweis im Senden-Modal
- web: Firmen-Website im Newsroom & Co. als absolute URL verlinkt
(Company::websiteUrl), statt relativ auf /newsroom/www.example.de
- kunde: API-Doku-Button öffnet die /de/api-Seite; OpenAPI-Spezifikation
als eigener, benannter Download (pressekonto-api-v1.yml)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Der aktive Firmen-Kontext (Firmen-Umschalter) steht jetzt prominent
unter der Überschrift — Firmenname oder „Alle Firmen" — statt als
kleiner Hinweistext. Neue Spalte „Bild" zwischen Status und Titel mit
dem Titelbild (thumb-Variante, is_preview zuerst), dem gewählten
Platzhalter bei bildlosen PMs oder einem generischen Icon-Fallback.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Neuer Button in der Aktionsspalte und im Detail-Modal führt zu
admin/users/{id}; bestehende Aktions-Buttons bekommen Tooltips.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Neuer ActivityType "Zur Zahlung": Der StripeCheckoutService vermerkt
zentral in allen vier Checkout-Erzeugern (Abo, Einzel-PM, Depublikation,
Credit-Topup), wenn ein User zu Stripe weitergeleitet wird — mit Art
und Referenz in den Metadaten. Der Abschluss war bereits abgedeckt
(Webhook: "Zahlung eingegangen" mit Rechnungsnummer/Betrag, Abo-Events
als "Abo", Wallet-Aufladungen als "Credit-Aufladung").
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Neues Panel "Guthaben & Kontingent" auf der Benutzer-Detailseite
(users:manage): Credits gehen als Grant-Buchung ins Wallet-Ledger,
freie Pressemitteilungen als bezahlte 0-€-Einzelkäufe, die das
Buchungs-Gate erfüllen und beim Veröffentlichen regulär eingelöst
werden — Stripe bleibt unberührt. Jede Gutschrift landet mit Vergeber
und Grund als neuer Typ "Gutschrift" im Aktivitäts-Verlauf.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Neuer ActivityType "Mail-Abmeldung": Der Unsubscribe-Controller
protokolliert Abbestellen (Link-Klick und RFC-8058-One-Click) sowie
das Wieder-Aktivieren — nur bei echter Zustandsänderung, wiederholte
Klicks erzeugen keine Doppel-Einträge.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Neues Modul unter Admin ▸ Newsletter (nur users:manage): Kampagnen mit
Betreff + leicht formatiertem Text (Purifier-Profil newsletter: fett/
kursiv, Listen, Links) im zentralen Mail-Layout. Empfänger-Filter auf
der User-Datenbank: Portal, Legacy-Vertrag (Bestandsschutz), Login
innerhalb/außerhalb X Monaten, Sortierung nach Login/Erstellung — mit
Live-Zähler und Vorschau im Editor.
Versand friert die Empfängerliste als Protokoll ein (newsletter_
campaign_recipients) und stellt pro Empfänger einen Queue-Job ein;
der Opt-out wird im Worker erneut geprüft. Jede Mail trägt den
signierten One-Click-Abmelde-Link (neuer EmailNotificationType::
Newsletter, bestehende Unsubscribe-Infrastruktur inkl. RFC 8058);
die Abmeldung greift sofort für alle künftigen Auswahlen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Neuer Listener RecordMailActivity am MessageSent-Event erfasst zentral
alle Versandwege (Mailables, Notifications, Fortify, Queue-Mails beim
realen Versand im Worker). Neuer ActivityType "E-Mail" mit Empfänger,
Betreff und Mailable-Klasse in den Metadaten; bekannte Empfänger werden
mit ihrem User verknüpft.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Firma und Autor sind im Header und Status-Grid klickbar
(admin/companies/{id} bzw. admin/users/{id}). Das Kontakte-Panel heißt
jetzt "Firma & Pressekontakte" und zeigt die Firmendaten (Adresse,
E-Mail, Telefon, Website) über den Pressekontakten. Neu: "Benutzer mit
Zugriff (über Firma)" listet alle Firmen-Mitglieder mit Rolle und
Autor-Markierung — mehrere User hängen nur über die Firma an einer PM,
Magic-Links sind reine Login-Tokens.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Das System ist seit 20.07.2026 live; die dev-Doku spiegelt jetzt den
Betriebszustand statt der Release-Vorbereitung:
- dev/betrieb/: Deploy-Runbook (neu: §10 Betrieb nach Go-Live mit
SFTP-Deploy-Checkliste inkl. queue:restart-Pflicht und den
Betriebs-Erkenntnissen der Go-Live-Woche), Legacy-API-Parität, Umami.
- dev/roadmap.md: offene Nachlauf-Punkte (Go-Live-Mails, Merge nach main,
Stripe-Test-Artefakte, Key-Rotation, Alt-System-Abschaltung) und
Ideen-Backlog.
- dev/archive/: abgeschlossene Phasen — Migration-2026-Planung,
Cutover-Runbook (mit Erledigt-Vermerk), Release-Plan/QA-Checkliste,
Optimierungsphasen, Frontend-Konzepte + Responsive-Audits vom 16.07.,
Altsystem-Reste.
- dev/README.md als Wegweiser; gitignore-Pfad der SQL-Dumps mitgezogen
(dev/archive/migration-2026/sql/ bleibt untracked, ebenso dev/migration/
und dev/api/).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Eigene Icon-Sets unter public/icons/{pressekonto,presseecho,businessportal24}
(SVG-Favicon + Touch-/App-Icons); ThemeHelper mappt das aktive Theme auf den
Icon-Ordner (Portal/Admin nutzt den pressekonto-Satz), alle Layout-Heads
binden Favicon + apple-touch-icon domain-abhängig ein.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ploi kann nur Pfad-Redirects, daher als App-Middleware (früh im globalen
Stack): GET/HEAD auf www.<domain> → 301 auf die Apex-Domain mit Pfad und
Query. Unsichere Methoden werden nicht umgeleitet (301 verwirft den Body)
— die beantwortet die App dank Host-Normalisierung weiterhin selbst.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Die Domain-Auflösung matchte den Host exakt gegen domain_name —
www.presseecho.de fiel dadurch aufs Portal-Default-Theme zurück, dessen
theme-portal.css im Web-Build nicht existiert. Alle Route::view-Seiten
ohne Controller (Impressum, Datenschutz, AGB, FAQ, …) antworteten auf
www-Hosts mit 500 (ViteException, Dauereintrag im Live-Log); die
Controller-Seiten traf es nicht, weil WebPortalContext den Host tolerant
matcht. resolve() normalisiert Hosts jetzt (lowercase, www. entfernt) auf
beiden Seiten des Vergleichs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Der Livewire-Feed lief auf jeder Startseiten-Impression ungecacht; seine
ORDER-BY-published_at-Query kann wegen portal IN (…) keinen bestehenden
Index zur Sortierung nutzen und filesortete ~10-20k Zeilen (live gemessen
2,3 s pro Aufruf — die Startseite blieb dadurch trotz Seitendaten-Cache
bei 6-9 s TTFB). Zwei Maßnahmen:
- Neuer Index (language, status, deleted_at, published_at): Backward
Index Scan statt Filesort, Query 2 ms statt 2,3 s — greift auch für
ungecachte Livewire-Interaktionen (Zeitraum-Filter, tiefe Seiten).
- Feed-Seiten 1-3 laufen über WebPageCache (SWR wie die übrigen
Seitendaten); gecacht werden nur items + total, der Paginator wird pro
Request neu gebaut (keine Host-URLs des Cache-Erzeugers im Cache).
Unbekannte timeframe-Werte aus der URL fallen auf 'all' zurück und
erreichen weder Query noch Cache-Key. Der doppelte Gesamt-COUNT pro
Request entfällt; der 'Mehr laden'-Zähler nutzt jetzt den gefilterten
Total des Paginators.
Lokal mit Live-Datenbestand: Startseite kalt 875 ms (vorher 2,5 s),
warm 11 ms (vorher 72 ms).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Start-/Kategorieseiten bauten nach Ablauf der 5-Minuten-TTL im Besucher-
Request neu (~25-30 Archiv-Queries über ~100k PMs) — Long-Tail-Rubriken
waren dadurch praktisch bei jedem Aufruf langsam. WebPageCache nutzt jetzt
Cache::flexible([300, 3600]): Innerhalb 5 Min frisch wie bisher, danach
wird bis zu 1 h sofort der alte Stand serviert und der Neuaufbau läuft
nach der Response (deferred). HomePageData/CategoryPageData/TickerData
laufen dafür über die gemeinsame WebPageCache-Konvention (Keys unverändert),
die Hub-Kennzahlen über flexible([3600, 14400]).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kunden, die in beiden Altportalen existierten, tragen nach dem Merge nur
das zuletzt importierte Portal in users.legacy_portal/legacy_id — der
Schlüssel des anderen Portals fand dadurch keinen Besitzer und wurde als
'Kein User-Mapping' übersprungen (Presseecho-Key → 410). Die Import-Map
kennt beide Zuordnungen und wird jetzt zuerst befragt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
--since=<Zeitpunkt> schränkt den Quell-Scan auf Legacy-Zeilen ein, deren
created_at ODER updated_at im Fenster liegt (users/companies/contacts/
press-releases; Kategorien und link-associations bleiben ungefiltert).
Kombiniert mit --force werden geänderte Bestandszeilen aktualisiert —
damit dauert ein Tages-Delta Sekunden statt eines Volldurchlaufs über
150k+ Zeilen.
Dazu Bugfix, der den Delta erst rund macht: User-/Company-/Contact-
Importer übergaben created_at/updated_at innerhalb von updateOrCreate —
die Felder sind nicht fillable und wurden stillschweigend verworfen
(Live-Befund: NULL-Timestamps, bisher nur durch legacy:fix-timestamps
geheilt). Jetzt explizit per forceFill wie im PressReleaseImporter.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cutover-Runbook: Legacy-DBs liegen bei KAS und sind als lokale Snapshot-
Kopien eingespielt (Delta-Läufe brauchen vorher frische Dumps); privatize-
media läuft mit --prune-missing; retire-agency-type als Satellit ergänzt;
verlorene Presseecho-PM-Bilder (~22k) als bewusste Entscheidung dokumentiert.
Deploy-Runbook: Seeder-Liste um LegalDocumentSeeder/RejectionCategorySeeder
ergänzt, billing:sync-stripe-plans nach frischem Seeding, Tinker-Befehl für
die Admin-Rolle.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kündigung zum Periodenende, deren Rücknahme (customer.subscription.updated,
Transition über previous_attributes.cancel_at_period_end) und das endgültige
Abo-Ende (customer.subscription.deleted) erscheinen jetzt als neuer Typ
"Abo" unter Admin ▸ Reports ▸ Verlauf — bisher war dort nur die Zahlung
sichtbar, die Portal-Kündigung blieb unsichtbar.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Die Cross-Schema-JOINs zeigten fest auf `presseecho`/`businessportal` —
auf dem Live-Server heißen die Schemas aber anders (lokale Snapshot-Kopien
der KAS-Datenbanken). Der Schema-Name kommt jetzt aus der database-Config
der jeweiligen Legacy-Connection (DB_DATABASE_PRESSEECHO/_BUSINESSPORTAL),
derselben Quelle, die auch legacy:import nutzt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Der Legacy-Import legt pro Alt-Bild eine Zeile mit rohem Legacy-Dateinamen
als path an; legacy:migrate-media biegt sie auf die echte Datei um. Zeilen,
deren Datei im Alt-System verloren ging (Live-Befund: ~22k bei Presseecho),
blieben als disk=public-Leichen liegen und ließen den Privatizer mit
RuntimeException abbrechen. Jetzt: Skip + Warnhinweis statt Abbruch;
--prune-missing löscht solche Zeilen endgültig, wenn ihre Datei auf keiner
Disk existiert. Der Integritäts-Assert für migrierte Pfade bleibt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Der Nachweis-PDF bindet nun das Titelbild der PM kompakt oben ein – links
das Bild mit den beim Upload erfassten Bildrechten (Copyright, Urheber,
Lizenz, Quelle, KI-Kennzeichnung), rechts daneben die Angaben zur
Veröffentlichung als Tabelle. Der Volltext übernimmt Fett-/Kursiv-
Formatierung (F2/F3/F4), und Fortsetzungsseiten beginnen den Inhalt mit
Abstand unterhalb des Seitenrands statt direkt am Seitenanfang.
Co-authored-by: Cursor <cursoragent@cursor.com>
Inhaltliche Korrektur (8 Credits) und Update/Ergänzung (4 Credits)
existieren als buchbare Leistungen noch nicht — sie folgen erst mit dem
Bookings-/Magic-Link-Rework und fliegen bis dahin von der Preisliste.
Depublizieren wird als Direktkauf in Euro abgerechnet (Stripe-Checkout
über billing.depublication_price_cents), nicht in Credits: Die Seite
zeigt jetzt 35 € aus derselben Preisquelle wie der Kauf-Flow statt der
falschen 35 Credits. Ohne konfigurierten Preis verschwindet der Block
komplett (wie das Angebot auf der PM-Detailseite).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ergänzt die bislang fehlenden strukturierten Daten auf den Presse-Seiten:
- PM-Detailseite: NewsArticle + BreadcrumbList als JSON-LD sowie
article:published_time/-section/-tag-Metas und og:image:alt — nur auf
der öffentlichen Seite, die noindex-Vorschau bleibt ohne Markup.
- Kategorie-Seiten: BreadcrumbList plus hreflang de/en über die
lokalisierten Slugs (CategoryPageData liefert dafür neu
categoryAlternateSlugs, mit View-Fallback für gecachte Alt-Daten).
- Newsroom: Organization-Markup (Logo, Website als sameAs, Boilerplate).
- Portal-Homepages: WebSite mit SearchAction (Sitelinks-Searchbox) und
Organization.
- Layout: og:locale und twitter:image ergänzt; neue Sections meta_extra
und structured_data.
Neue Komponente x-web.structured-data rendert JSON-LD mit JSON_HEX_TAG,
damit "</script>" in Titeln/Teasern das Script-Tag nicht aufbrechen kann.
JSON-LD-URLs werden wie der Canonical aus der Portal-Basis gebaut
(unabhängig von forceRootUrl), damit Host und Canonical übereinstimmen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Behebt strukturelle Mobile-Bugs (320/390px) über presseecho,
businessportal24 und den pressekonto-Hub hinweg:
- Focus-/Kategorie-/Newsroom-Hero: Text im Flow (min-h + flex) statt
fester Höhe, responsive Typo — Overlay überläuft nicht mehr nach oben.
- Quality-Summary: Grid stackt auf Mobile (behebt Seiten-Overflow).
- Hub: Utility-Bar mit Wrap, Header mit Mobile-Burger, Footer stackt,
Architektur-Diagramm als gestapelte Mobile-Variante.
- Sticky-Header (Portal): fehlende .header-sticky-CSS ergänzt und Script
mit Platzhalter, damit der Header beim Scrollen sichtbar bleibt.
- Header-Overflow bei 320px entschärft (Portal + Hub); Hub-Hero-H1
verkleinert, damit die Wortmarke "businessportal24" umbricht.
- Mobile-Nav um Preise/FAQ/API ergänzt; Industry-Index-Dichte,
Touch-Targets und Preise-Einzel-PM-Block für kleine Screens verbessert.
- Homepages/Shared: px-8 → px-4 sm:px-6 lg:px-8.
Co-authored-by: Cursor <cursoragent@cursor.com>
Lange Kategorienamen liefen auf schmalen Viewports aus dem Layout —
Badge darf jetzt umbrechen und ist mobil auf 8rem begrenzt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rechts neben der pressekonto-Wortmarke erscheinen jetzt presseecho
und businessportal24 — Empfänger ordnen System-Mails damit sofort
den Portalen zu. Hellere Markenfarben auf Hub-Blau für Lesbarkeit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Chronologisches Protokoll der Systemereignisse als neue Admin-Seite
(/admin/reports/activity) mit Filter nach Ereignistyp, Zeitraum und
Suche. Erfasst ab jetzt fortlaufend:
- Registrierungen inkl. Weg (Formular, Google, Magic-Link)
- Anmeldungen inkl. Weg (Passwort, Google, Magic-Link, Impersonation)
- PM-Statuswechsel mit Ergebnis/Begründung (zentral im Status-Log-Hook;
KI-/Scheduler-Wechsel erscheinen als „System")
- Zahlungseingänge (Rechnungsspiegelung) und Credit-Aufladungen
- Formular-Eingänge: Kontakt, Rechtsanfrage, Widerruf, Kündigung,
Newsletter-Anmeldung (Observer auf den Modellen)
Schreiben ist Best-Effort über den ActivityLog-Service — ein
fehlgeschlagener Verlaufseintrag bricht nie den fachlichen Ablauf.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drei Stellschrauben für die Stripe-Rechnung (Befund Test-Buchung:
englischer Beleg, Position nur 'Starter'):
- stripePreferredLocales() am User: Stripe rendert Rechnung/Quittung
in der Kontosprache (de-DE, bei language=en englisch)
- Checkout-Session mit locale in der Kontosprache statt Auto-Erkennung
- billing:sync-stripe-plans vergibt sprechende Produktnamen inkl.
PM-Kontingent und zieht sie bei bereits verknüpften Plänen an den
bestehenden Stripe-Produkten nach (Preise bleiben unverändert)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Interner Beleg über jede versendete Buchungsbestätigung im
Rechts-/Service-Postfach (config legal.contact_email).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sobald eine bezahlte Stripe-Rechnung in den STR-Kreis gespiegelt
wird, erhält der Kunde eine Bestätigungsmail mit Rechnungsnummer
und Beträgen — deckt Tarif (inkl. Verlängerung), Einzel-PM und
Credits ab. Transaktionsmail ohne Opt-out; durch die idempotente
Spiegelung genau eine Mail pro Beleg, Gutschriften bleiben stumm.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
checkout.session.completed feuert bei asynchronen Zahlarten schon
nach dem Redirect mit payment_status=unpaid — der Listener hat
trotzdem sofort erfüllt (Credits gutgeschrieben / Kauf bezahlt),
auch wenn die Zahlung später scheitert. Erfüllung jetzt nur bei
payment_status paid/no_payment_required, zusätzlich reagiert der
Listener auf checkout.session.async_payment_succeeded; Fehlschläge
werden geloggt, der Kauf bleibt wiederholbar auf Pending.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mit BASIC_AUTH_ENABLED=true (Staging) bekam Stripe für jeden
Webhook-Call ein 401 — Zahlungen wurden nie erfüllt. Der Endpoint
ist durch die Signaturprüfung (STRIPE_WEBHOOK_SECRET) geschützt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Die beiden oberen Panels auf /admin/me/profile hatten als einzige
keine eigene Speichern-Zeile — Sprach- und Kontaktdaten-Änderungen
ließen sich nur über die Buttons weiter unten sichern. Footer wie in
den übrigen Panels ergänzt, per flex/mt-auto bündig am Panel-Ende.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Die Register-Komponente setzte kein registration_type, wodurch der
DB-Default existing_legacy griff und jeder Neukunde im Admin als
Legacy-Bestand erschien. Jetzt wird explizit Company gesetzt —
konsistent mit dem CustomerAccountProvisioner.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Neue aufklappbare "Erste Schritte"-Card als erste Card im Kunden-Dashboard,
die neue Nutzer durch Profil, Rechnungsadresse, Firma und erste PM führt und
sich ausblendet, sobald alle Schritte erledigt sind. Zusätzlich die
gemeinsame Fehlerseiten-Vorlage (404/403/419/500/503) an das Hub-Design
angepasst (Palette, Gold-Akzent, Hub-Blau-Button), weiterhin self-contained
ohne Vite-Assets.
Co-authored-by: Cursor <cursoragent@cursor.com>
- dev/optimize/optimize-14-07-2026.md: Security-/Code-Audit inkl.
Fix-Protokoll (Befunde gegen den Code verifiziert).
- docs/weiteres/KI-Kennzeichnung.md, Pm Tracken.md, Decision-Updates und
Release-/Entwicklungsplan aktualisiert.
Hinweis: Der Audit-Bericht enthält keine echten Geheimnisse (nur die
öffentlichen Key-Präfixe), aber eine vollständige Befundübersicht.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Neue Reports-Seite (settings:manage) zeigt den Queue-Zustand (ältester
wartender Job, Worker aktiv/gestoppt, fehlgeschlagene Jobs) und
Deployment-Checks als Ampel: Permissions ausgerollt, APP_DEBUG,
Session-Secure-Cookie, ausstehende Medien-Privatisierung. Damit ist die
für Magic-Link-Login und fristrelevante Mails kritische Queue-Abhängigkeit
aus dem Admin sichtbar.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Navigations-Chrome (CategoryNavigation), Suche- und Newsroom-Seitendaten
300s gecacht; gemeinsamer WebPageCache-Helper (eine TTL/Key-Konvention).
- Cache-Poisoning über ?page= geschlossen (Key an Paginator ausgerichtet,
kein withQueryString im Cache); Newsrooms cachet nur den ungefilterten
Fall und die ersten Seiten (kein unbegrenzter Key-Raum).
- Detailseiten schreiben Hits/View-Events deferred nach der Response
(M12); OpenAI-/Markt-/USt-ID-Aufrufe mit Connect-Timeouts und
begrenzten Retries; Admin-Memo-Caches konsistent invalidiert.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
updateOrCreate ersetzt durch eine Ownership-Prüfung: ein fremder,
gesetzter Eigentümer wird abgelehnt (409), bestätigte Abos werden nicht
still ent-bestätigt. Verwaiste Datensätze (user_id NULL nach Konto-
Löschung/Legacy-Import) dürfen neu gebunden werden, statt dauerhaft
gesperrt zu bleiben.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Magic-Link-Login übergibt bei aktiviertem 2FA an die 2FA-Challenge,
statt sie zu umgehen (M1).
- Bestätigungs-/Benachrichtigungs-Mails (Kontakt, Recht, Kündigung,
Widerruf, Magic-Link) laufen als ShouldQueue+afterCommit über die Queue,
damit die Response nicht am SMTP-Roundtrip hängt.
Hinweis: Der Queue-Worker muss im Betrieb laufen und überwacht werden, da
fristrelevante Bestätigungen darüber zugestellt werden (siehe
Betriebsüberwachung).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
page_url aus dem anonymen Kontaktformular wird auf url:http,https
validiert; das Admin-Backend rendert nur http(s)-Links klickbar, sonst
als Text. Verhindert javascript:-URIs, die beim Admin-Klick im
Admin-Kontext liefen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- users:manage/roles:manage auf allen User-/Rollen-Masken erzwungen;
is_super_admin aus $fillable entfernt und nur per autorisiertem
forceFill setzbar; zentraler Gate::before-Super-Admin-Kurzschluss (K1/M8).
- Nicht-Super-Admins können Super-Admin-Konten nicht mehr bearbeiten (Fix#2);
Impersonation mit Rangprüfung, keine Token-Erstellung während Impersonation (H1).
- Destruktive/verwaltende Admin-Aktionen (CRM/Billing/Compliance/Struktur)
hängen an neuen admin-only Permissions; Editor behält reine Content-Rechte (H2).
- Kanonischer Permission-Katalog (App\Authorization\Permission) als Single
Source of Truth für Seeder und Monitoring.
Deployment: `php artisan db:seed --class=RolesAndPermissionsSeeder` auf allen
Umgebungen ausführen (neue Permissions).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>