Commit graph

532 commits

Author SHA1 Message Date
215cbcbdbf admin: internes Newsletter-Modul mit Filtern, Protokoll und Abmeldung
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>
2026-07-21 09:26:57 +00:00
d411e6ce7f admin: Verlauf protokolliert jede versendete E-Mail
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>
2026-07-21 09:13:43 +00:00
e3ce626b73 admin: PM-Detailseite verlinkt Firma, Autor und Firmen-Benutzer
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>
2026-07-21 08:48:22 +00:00
fa303360ee docs: R6.5 in der Roadmap abgehakt (Merge nach main + Tag v1.0.0)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 07:58:20 +00:00
8743bcb39d Release: Go-Live Pressekonto (20.07.2026)
Merge von fix/zeitzone-europe-berlin nach main — kompletter Stand des
Go-Lives: Legacy-Migration (Import, Medien, Timestamps, Verify grün),
Stripe-Live-Betrieb, Legacy-API-Kompatibilität inkl. Key-Import,
Performance-Fixes der öffentlichen Seiten (SWR-Caching + Feed-Index),
kanonische www-Redirects, Favicons, Betriebs-Doku (dev/betrieb, Roadmap).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 07:55:59 +00:00
783b9da9b6 docs: verschobene Doku-Pfade in Code-Kommentaren nachgezogen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 07:53:08 +00:00
6a4c4d8837 docs: dev-Doku für den Live-Betrieb neu sortiert — Archiv + Betrieb + Roadmap
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>
2026-07-21 07:52:38 +00:00
58c773b153 web: Domain-spezifische Favicons und App-Icons für alle drei Marken (R5.4)
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>
2026-07-21 07:50:21 +00:00
42e56a7d81 web: www-Hosts leiten kanonisch per 301 auf die Apex-Domain
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>
2026-07-20 15:47:13 +00:00
acf67211bf web: www-Hosts lösen aufs Apex-Domain-Theme auf statt aufs Portal-Fallback
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>
2026-07-20 15:13:33 +00:00
3d10cbf709 web: Startseiten-Feed cachen + Index gegen Filesort der Feed-Query
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>
2026-07-20 14:53:09 +00:00
bd4ad855ba web: Stale-while-revalidate-Cache für die öffentlichen Seitendaten
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>
2026-07-20 14:29:15 +00:00
5011f28f88 legacy: API-Key-Import löst E-Mail-gemergte User über legacy_import_map auf
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>
2026-07-20 13:19:42 +00:00
d212230a39 docs: Cutover-Delta nutzt --since, Medien-Delta-Hinweis
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 13:03:35 +00:00
a0c2e663dc legacy: --since-Delta für legacy:import + Importer setzen Timestamps wieder korrekt
--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>
2026-07-20 13:03:07 +00:00
22b7bd38b7 docs: Runbooks auf den echten Migrationsstand nachgezogen (16.07.)
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>
2026-07-20 10:50:59 +00:00
9be473162a billing: Abo-Kündigungen aus dem Stripe-Portal im Aktivitäts-Verlauf
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>
2026-07-20 10:50:47 +00:00
7971656025 legacy: fix-timestamps liest Legacy-Schema-Namen aus der DB-Config statt hartkodiert
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>
2026-07-20 10:50:39 +00:00
3151b1f38a legacy: privatize-media überspringt unmigrierte Legacy-Bildzeilen, --prune-missing löscht sie
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>
2026-07-20 10:50:31 +00:00
a1302994c5 billing: Veröffentlichungsnachweis mit Titelbild, Bildrechten und Rich-Text
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>
2026-07-16 12:05:46 +00:00
07d89a0e6a web: Preise-Seite zeigt unter Weitere Leistungen nur noch Depublizieren
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>
2026-07-16 11:34:22 +00:00
2ea64ad222 web: Strukturierte Daten (JSON-LD) und SEO-Metas fürs öffentliche Frontend
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>
2026-07-16 10:27:45 +00:00
dec524989b web: Responsive-/Mobile-Fixes fürs öffentliche Frontend
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>
2026-07-16 09:33:49 +00:00
9c67fc6c14 docs: Cutover-Runbook für den Go-Live (Legacy-Import & Umschaltung)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 16:07:13 +00:00
3e855375b1 web: Kategorie-Badge im Feed bricht auf Mobile sauber um
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>
2026-07-15 16:07:05 +00:00
a3671bab90 emails: Portal-Wortmarken im Mail-Header ergänzen
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>
2026-07-15 16:06:59 +00:00
3a11963990 admin: Aktivitäts-Verlauf unter Reports ▸ Verlauf
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>
2026-07-15 16:00:58 +00:00
a574898ca9 billing: Stripe-Belege auf Deutsch und sprechende Produktnamen
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>
2026-07-15 15:25:07 +00:00
37bcf731bf billing: Blindkopie der Zahlungsbestätigung an LEGAL_CONTACT_EMAIL
Interner Beleg über jede versendete Buchungsbestätigung im
Rechts-/Service-Postfach (config legal.contact_email).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 15:00:20 +00:00
e32431e948 billing: Zahlungsbestätigung per Mail nach jeder Buchung
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>
2026-07-15 14:36:37 +00:00
425467bbec billing: asynchrone Zahlarten (PayPal, SEPA) korrekt erfüllen
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>
2026-07-15 14:27:09 +00:00
67f6906b87 billing: Stripe-Webhook von der Basic-Auth ausnehmen
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>
2026-07-15 13:55:32 +00:00
0b01929f80 profil: Speichern-Buttons für Persönliche Daten und Konto & Sicherheit
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>
2026-07-15 13:12:05 +00:00
ce59cbbf4a auth: Selbst-Registrierungen nicht mehr als Legacy-Bestand einstufen
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>
2026-07-15 13:11:53 +00:00
700f5e6131 dashboard/errors: Onboarding-Card „Erste Schritte" & Hub-Styling der Fehlerseiten
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>
2026-07-15 13:07:36 +00:00
74f71f7a58 docs: Audit-Protokoll, KI-Kennzeichnung & Release-Notizen
- 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>
2026-07-14 13:50:41 +00:00
e51fe6ffa9 chore: persönliche VS-Code-Workspace-Datei aus dem Repo entfernen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:50:31 +00:00
830aca5149 admin: Betriebsüberwachung im Backend (Queue-Worker & Deployment-Checks)
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>
2026-07-14 13:49:57 +00:00
23cbd0bfe2 performance: Caching, deferred Tracking & externe Timeouts (H7, M12, M13)
- 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>
2026-07-14 13:49:47 +00:00
5a492926dd newsletter: Bestätigung/Ownership beim Re-Abo korrigieren (M7)
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>
2026-07-14 13:49:31 +00:00
fe1525b9ff auth/mail: Magic-Link-2FA (M1) + Mailversand über die Queue
- 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>
2026-07-14 13:49:21 +00:00
8f517e993d security: Stored-XSS über page_url im Kontaktformular schließen (H3)
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>
2026-07-14 13:49:05 +00:00
77f8f9cb35 security: Rechteausweitung schließen & Admin-Panel feingranular gaten (K1, H1, H2, M8)
- 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>
2026-07-14 13:48:50 +00:00
39e13d6d84 security: Host-Whitelist & Legacy-API-Key-Übergabe härten (H5, M11)
- trustHosts erlaubt nur die konfigurierten Domains (plus localhost/IP
  für Health-Checks) und verwirft fremde Host-Header, sodass
  X-Forwarded-Host keine URL-Generierung mehr vergiften kann (H5).
- Legacy-API-Keys nur noch per Header (Bearer/X-ApiKey); die alte
  Query-/Body-Übergabe wird nicht mehr zur Auth akzeptiert, erhält aber
  weiterhin den 410-Migrationshinweis statt eines opaken 401 (M11).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:48:28 +00:00
6250b0bab4 media: PM-Medien privat ausliefern + Vorschau-Signatur (M4, M5, Fix#4/#5)
- Neue PM-Bilder/Anhänge liegen auf der privaten local-Disk; Auslieferung
  über kontrollierte Media-Routen mit gemeinsamer Zugriffslogik
  (AuthorizesPressReleaseMediaAccess). Öffentliche Medien werden aus den
  geladenen Modellen ermittelt (weniger Queries) und länger gecacht.
- Anonyme Token-Vorschau lädt Bilder über temporär signierte URLs (Fix#5).
- API-Resource baut Variant-URLs über die Media-Route statt /storage
  (Fix#4). Storage-Schicht prüft zusätzlich den serverseitigen MIME-Type (M5).
- press-releases:privatize-media migriert Bestandsmedien.

Deployment: einmalig `php artisan press-releases:privatize-media` ausführen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:48:06 +00:00
6e84266f90 press-releases: Publish-Rechte, Quota-Races & Slug vereinheitlichen
- Publish/Reject/Archive und changeStatus erzwingen konsistent
  press-releases:publish; changeStatus schloss die Lücke, über die das
  Status-Modal die Prüfung umging (Fix#1/H2).
- publish()/changeStatusFromAdmin sperren PM- und User-Zeile
  transaktional; QuotaExceededException wird in Admin-Publish und im
  Auto-Publish-Job abgefangen (Warnung statt 500 / bleibt in Review, Fix#3/M2).
- Slug-Erzeugung nur noch über HasUniqueSlug (generateUniqueSlug +
  saveWithUniqueSlugRetry), redundanter Controller-Helper entfernt (M3).
- API-/Legacy-Text wird vor der Persistenz sanitisiert (M6);
  Scheduler/Legacy-Tracking ignorieren soft-gelöschte PMs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:47:51 +00:00
aae456954a billing: Nebenläufigkeit & Idempotenz härten (H4, Stripe, Boost-Slots)
- CreditTopup-Gutschrift, MAN-Rechnung, Proof-PDF und Stripe-Webhook-
  Spiegelung sperren die betroffene Zeile innerhalb der Transaktion und
  prüfen die Idempotenz erst danach (verhindert Doppelgutschrift bei
  at-least-once-Webhooks, H4).
- Gemeinsamer LocksModelForUpdate-Trait für die häufige Lock-by-Key-Form
  (CreditTopup, ManualInvoice).
- Unique-Index auf invoices.stripe_invoice_id; Boost-Inventar über
  boost_slot_locks serialisiert, verwaiste Kategorie-Locks werden beim
  Löschen der Rubrik entfernt.

Deployment: vor migrate prüfen, ob invoices.stripe_invoice_id bereits
Duplikate enthält (die Unique-Migration bricht sonst bewusst ab).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:47:21 +00:00
3295f47556 deps: Sicherheits-Updates (Framework/Symfony/Guzzle) einspielen (H6)
composer update auf gepatchte Minor-Versionen gegen die im Audit
gemeldeten Advisories (u. a. symfony/http-kernel CVE-2026-45075,
symfony/mime CVE-2026-45067, laravel/framework <12.60). composer audit
ist danach clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:47:07 +00:00
6f96e4a4cc PM-Detailseite: Inhaltspruefung (Gruen/Gelb/Rot) + Qualitaets-Score sichtbar
- Neue Kachel "Inhaltspruefung" in Status & Verlauf (Badge je Ergebnis,
  "Ausstehend" ohne Pruefung); bei Gelb neutraler Hinweis, dass die
  Boost-Platzierung gruen gepruefte Meldungen voraussetzt
- Content-Score-Panel des Editors in gemeinsame Komponente
  x-content-score-panel extrahiert und auf der Detailseite wiederverwendet
  (Score/100, Stufe, Abstand zur naechsten Schwelle)
- PressReleaseClassification::badgeClass() (ok/warn/err)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 14:59:33 +00:00
4f2be1e0a8 PM-Uebersicht: Rubrik unter Portal, Boost-moeglich-Hinweis, gruener Haken
- Rubrik (lokalisiert) direkt unter den Portal-Pills; Kategorie wird in
  accessiblePageFor() eager geladen (kein N+1)
- Status-Spalte: laeuft kein Boost, wirbt eine veroeffentlichte gruene PM
  mit "Boost moeglich ->" (Link zur PM-Seite mit den Add-ons)
- Gruen geprueft: kleiner gruener Haken neben dem Titel (Tooltip
  "Inhaltspruefung: Gruen")

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 13:23:07 +00:00