Commit graph

49 commits

Author SHA1 Message Date
5c91e2e281 API: KI-Deklaration als v1-Pflichtfeld, Legacy-Default fuer /service (Entscheidung 10.07.)
Befund: v1 hatte keine KI-Deklarationsfelder — Submit lief seit der
KI-Kennzeichnungspflicht (09.07.) immer auf 422 und haette alle
Legacy-API-Kunden blockiert (Suite uebersah es: Factory deklariert).

- v1-Submit verlangt ai_text_declaration; der Call gilt dokumentiert
  als verbindliche Bestaetigung (Art. 50 KI-VO) und schliesst die
  Archiv-Kenntnisnahme (AGB par. 13) ein
- Bild-Upload verlangt ai_declaration (original/ai_generated/ai_edited)
- Legacy-/service-Aliasse defaulten auf none/original — das
  API-Kunden-Mailing (R6.4) weist auf die Erklaerungswirkung hin
- OpenAPI v1.yml + lang-Hinweis aktualisiert; Tests erweitert
  (Submit/Image/LegacyCompatibility)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 12:41:24 +00:00
644f860223 PM-Tracking: Ereignis-Log fuer Aufrufe + Shares (press_release_events)
- press_release_events (type view/share, channel, ohne Personenbezug):
  Model/Enums/Factory, Cascade-Delete an der PM
- Detailaufruf loggt View-Event zusaetzlich zum Legacy-hits-Zaehler
  (bleibt bewusst einfacher Page-Request-Zaehler, User-Vorgabe 10.07.)
- POST press-release/{slug}/share (throttle 30/min, nur archiv-sichtbare
  PMs des Portals) loggt Teilen-Klicks je Kanal (LinkedIn/X/E-Mail/Copy)
- Kundenpanel-PM-Seite: neue Kachel Geteilt mit Kanal-Aufschluesselung
- lang: web.detail.views (de/en)
- Tests: Web/PressReleaseTrackingTest (7)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 09:56:42 +00:00
72e6a3c210 Backend-WIP: Kontakt-Queue, Page-Cache, Validierungs-Feedback, i18n
Gebuendelt, da ueber gemeinsame lang-/Service-Dateien verschraenkt:
- Kontakt-Nachrichten: contact_messages-Tabelle + Model/Enums/Factory,
  Admin-Queue (livewire/admin/contact-messages), ContactController
  persistiert das Anliegen zusaetzlich zur Brand-Mail
- Page-Data-Cache (Services/Web/*PageData) + web-archive-Indizes auf
  press_releases
- Validierungs-Feedback + Company-Portal-Restriction (Feature-Tests)
- i18n-Auslagerung lang/de|en (auth, passwords, validation, static)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:38:11 +00:00
56fe5ea6f4 Attribution: Übermittler aus users.agency_name statt Agentur-Firma
A3 des Umbaus Agentur-Attribution → User-Profil: Eager Loading der
Detailseite/Vorschau lädt user:id,agency_name; die Legacy-Relation
PressRelease::submittingAgency() (Pseudo-Firma type=agency via
owner_user_id) entfällt. Tests decken Verteiler-Fall, Namensgleichheit
und PM ohne Übermittler ab.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:30:41 +00:00
1ef1e13c19 Depublikation: abgebrochener Checkout blockiert nicht mehr
Der Klick auf "Kostenpflichtig depublizieren" legte sofort einen
Pending-Kauf an; wer bei Stripe abbrach, sah danach dauerhaft "liegt
bereits ein Depublikations-Vorgang vor". Jetzt sperrt nur ein bezahlter
Vorgang — ein offener Pending-Kauf wird beim nächsten Klick
wiederverwendet (kein Duplikat), und die Detailseite zeigt das Angebot
weiterhin an.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:07:32 +00:00
ef2b4fe2ad Downgrades ohne Gutschrift: Tarifwechsel nach unten erst zum Periodenende
QA-Entscheidung 07.07.: Eine Buchung ist nicht rückabwickelbar — es darf
nie eine negative Proration-Rechnung oder ein Stripe-Guthaben entstehen.

- StripeCheckoutService::isDowngrade: sofortiger Wechsel nur, wenn der
  Periodenpreis des Ziel-Tarifs den aktuellen erreicht (Wechsel-Rechnung
  damit garantiert >= 0); sonst Downgrade
- scheduleDowngrade: Stripe Subscription Schedule stellt das Abo zum Ende
  der bezahlten Periode um (proration_behavior none), hebt eine
  vorgemerkte Kündigung auf und spiegelt die Vormerkung lokal
  (neue Spalten scheduled_stripe_price/scheduled_change_at/
  stripe_schedule_id an subscriptions)
- Sofort-Upgrade löst eine bestehende Vormerkung vorher auf (release)
- Webhook customer.subscription.updated räumt die Vormerkung nach
  Vollzug ab
- Buchungs-Modal unterscheidet Upgrade (sofort, Differenz-Rechnung) und
  Downgrade (Vormerkung, keine Zahlung heute); Banner + Hinweis auf der
  Paket-Karte zeigen den vorgemerkten Wechsel mit Datum
- Kontingent-Sperre aus 4b7f6d3 entfernt — ohne Gutschrift gegenstandslos

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:38:57 +00:00
4b7f6d3f0f Downgrade-Vorkehrungen: Kontingent-Sperre beim Tarifwechsel, Gutschriften nicht spiegeln
QA-Befund 07.07.: Der Wechsel zurück in einen kleineren Tarif erzeugte
eine Proration-Gutschrift (negative Stripe-Rechnung) — die STR-Spiegelung
crashte an den unsigned-Spalten (Webhook-500), und der Wechsel war trotz
verbrauchtem Monatskontingent möglich.

- ProcessStripeWebhook: Belege ohne positiven Rechnungsbetrag
  (Gutschrift/Null-Rechnung) werden geloggt übersprungen statt gespiegelt
- CheckoutController::swap: Wechsel gesperrt, wenn der Monatsverbrauch
  das Kontingent des Ziel-Tarifs übersteigt (möglich ab Monatserstem)
- Buchungs-Modal: erklärt die Sperre inkl. Datum statt Bestätigungs-
  Button; Proration-Hinweis deckt jetzt auch die Guthaben-Richtung ab

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:22:30 +00:00
090a893cc6 PM-Vorschau: echtes Portal-Design, Direkt-Button und Portal-Switch
Der Vorschau-Link (/pm-vorschau/{token}) renderte eine schlichte
Standalone-Seite, die dem spaeteren Erscheinungsbild nicht entsprach.
Jetzt rendert er die echte Detailseite (web.release-detail) im Theme
des Zielportals:

- Controller wendet Domain-/Theme-Konfig des Zielportals an (Sprache
  der PM, Links zeigen auf die Portal-Domain), kein Hit-Zaehler
- Gelbes Vorschau-Banner mit Status und Link-Gueltigkeit; noindex per
  Meta-Tag + X-Robots-Tag-Header, Canonical entfaellt bei noindex
  (neue robots-Section im Web-Layout)
- Teilen-Buttons teilen den Vorschau-Link selbst (oeffentliche URL
  existiert vor Veroeffentlichung noch nicht)
- PMs fuer beide Portale: ?portal=... waehlt die Ansicht, Umschalter
  im Banner; im Kundenpanel je Portal ein Vorschau-Button, sonst ein
  einzelner "Vorschau oeffnen"-Button (Link-Feld jetzt kopierbar)
- Alte press-release-preview-View entfernt

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 13:11:57 +00:00
9e76e24014 Tarifwechsel im Buchungs-Modal: Abo-Swap mit anteiliger Verrechnung
Tarif-Karten bieten bei bestehendem Abo jetzt 'Zu diesem Tarif
wechseln' (pro Intervall, aktueller Tarif deaktiviert). Der Wechsel
laeuft ueber das bekannte Bestaetigungs-Modal (T1neu-Checkbox) als
POST auf die neue checkout.swap-Route: Cashier swapAndInvoice stellt
sofort um, verrechnet anteilig und hebt eine vorgemerkte Kuendigung
auf. SCA-Faelle leiten auf die Cashier-Bezahlseite. QA-Befund 07.07.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:58:32 +00:00
7e571eed94 Rechnungsseite: neue STR/MAN-Rechnungen gelistet, STR bei Stripe verlinkt
Die Kundenseite /admin/me/invoices zeigte bisher nur das Legacy-Archiv.
Neue Sektion "Aktuelle Rechnungen" mit Netto/USt/Gesamt und Status;
STR-Rechnungen oeffnen die gehostete Stripe-Rechnungsansicht ueber
einen Redirect-Endpunkt (URL wird je Aufruf frisch geholt, Zugriff nur
fuer den Eigentuemer). KPIs zaehlen neu + Archiv zusammen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:01:12 +00:00
62fbafa4ce QA-Befunde A4: 2FA-Bestätigungsschritt + sicherer E-Mail-Wechsel
- 2FA: Einrichtung verlangt jetzt die Bestätigung mit einem App-Code
  (Fortify confirm-Semantik); vorher blieb two_factor_confirmed_at
  leer und der Login fragte nie nach der Challenge.
- Recovery-Codes nur noch einmalig sichtbar (nach Bestätigung bzw.
  Neu-Erzeugung), nicht mehr bei jedem Seitenaufruf.
- E-Mail-Änderung als Pending-Flow: Bestätigungslink geht an die NEUE
  Adresse, die alte bleibt bis zum Klick aktiv — kein Aussperren durch
  Tippfehler und keine Registrierungs-Notice mehr nach dem Ändern.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:53:24 +00:00
d0ffe664b8 QA-Luecke geschlossen: Legacy-Medien-URLs redirecten wieder (301)
Die Scope-Doku fuehrte den /thumbnails-Redirect als "ANGEPASST", im Code
existierte er nicht — alte Symfony-Bild-URLs (/thumbnails/..., /uploads/
...) liefen ins 404. Neuer LegacyMediaRedirectController mappt den
Original-Dateinamen (bleibt beim Medien-Import erhalten) auf die neue
Storage-URL fuer PM-Bilder und Firmenlogos; unbekannte Dateien bleiben
404. Gefunden bei der R6.1-Vorpruefung.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 10:12:35 +00:00
cc77e604f4 T12: Depublikations-Checkout mit Tombstone (config-gated)
Kunden koennen eigene veroeffentlichte PMs kostenpflichtig aus der
oeffentlichen Ansicht nehmen: Panel auf der PM-Detailseite (inkl.
T1neu-Unternehmer-Checkbox und Doppelausweis netto/brutto), Stripe-
Ad-hoc-Checkout wie Credit-Topups, Erfuellung im Webhook — die PM
wechselt auf archived + depublished_at, die oeffentliche URL zeigt
dauerhaft einen Tombstone (410 Gone, ohne Inhalte, mit ehrlichem
Drittarchive-Hinweis). Doppelbuchungs-Bremse pro PM; Status-Log mit
Quelle "depublication". Gate: ohne BILLING_DEPUBLICATION_PRICE_CENTS
bleibt der Weg deaktiviert und /melden zeigt die neutrale
Preisformulierung [EXTERN: Preis festlegen, O6].

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 09:46:25 +00:00
979e6b1ee1 T10: DSA-Meldeformulare mit URL-Feld, Vorgangsnummer und Eingangsbestaetigung
Zwei eigenstaendige Formulare unter /melden/rechtswidrig (DSA Art. 16:
URL, Name, E-Mail, Begruendung, Wahrheitsversicherung) und
/melden/kontaktdaten (DSGVO-Antrag) auf allen drei Domains. Gemeldete
URLs werden automatisch der passenden veroeffentlichten PM zugeordnet;
ohne Treffer bleibt die URL im payload. Jede Meldung erhaelt eine
Vorgangsnummer (M-{id}, analog K-{id} der Kuendigung), die meldende
Person unverzueglich eine Eingangsbestaetigung (DSA Art. 16 Abs. 4) und
das Melde-Postfach einen internen Hinweis — auch im bestehenden
Per-PM-Flow. Admin-Queue zeigt Vorgangsnummer, Name und gemeldete URL;
/melden verlinkt die neuen Formulare als ersten Weg.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 09:35:30 +00:00
6f9a68b63d T1neu: Unternehmer-Pflicht-Checkbox im Checkout, protokolliert mit AGB-Version
B2B-Gate (AGB v0.6 par. 1 Abs. 4): Das Buchungs-Modal verlangt vor jedem
Stripe-Checkout die Bestaetigung 'Ich bestelle in Ausuebung meiner
gewerblichen oder selbstaendigen beruflichen Taetigkeit (par. 14 BGB)'.

- booking_confirmations: user, Buchungstyp/-ref, AGB-Version, IP,
  confirmed_at (Nachweis laut Datenschutz Abschnitt 7)
- Checkout-Link erscheint erst nach der Checkbox; CheckoutController
  verlangt zusaetzlich serverseitig eine zeitnahe Bestaetigung (30 Min.)
  fuer Abo/Einzel-PM/Credit-Topup — Direktaufrufe laufen ins Leere
- config/legal.php als zentrale AGB-Versionsquelle (Seite + Protokoll,
  Uebergangsloesung bis zum legal_documents-System E3)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:17:59 +00:00
6438444a80 Datenschutzerklaerung v0.3 eingebaut, Cookie-Seite zurueckgebaut
- Datenschutz: vollstaendige v0.3 aus docs/legal, host-aware Bausteine
  (Hub: Kundenkonto/Login-Magic-Link/Stripe; Spokes: Medienprivileg,
  Newsroom, Aenderungslink-Variante); Systemwerte aufgeloest (Magic-Link
  15 Min. aus MagicLinkGenerator, Logs 14 Tage, Stripe Payments Europe);
  ersetzt den Platzhalter samt falscher Google-Analytics-Behauptung
- Cookie-Seite raus (Cookies stehen in Abschnitt 4 der Datenschutz-
  erklaerung, kein Banner noetig): Route -> 301 auf /datenschutz,
  Legacy-Alias cookies.html -> datenschutz, Footer + Sitemap bereinigt

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:17:59 +00:00
c195878295 R3.1: Rechtstexte eingebaut — Impressum, AGB v0.6, Richtlinien, Meldeverfahren
- Impressum: echte Anbieterdaten (adametz.media, Bielefeld, USt-ID) statt
  erfundener GmbH; DDG statt TMG, MStV-Verantwortlicher, Verbund-Absatz,
  DSA-Kontaktpunkt (Art. 11/12) mit neuem REPORT_EMAIL_*-Melde-Postfach
- AGB: B2B-Fassung v0.6 (03.07.2026) vollstaendig gerendert (par. 1-16,
  interne Anwalts-Anmerkungen entfernt, Variablen je Host aufgeloest);
  ersetzt den widerspruechlichen Platzhalter (30-Tage-Frist etc.)
- Neu: /richtlinien (Rahmentext + Ablehnungskategorien K1-K6 mit Ankern,
  statisch bis zum rejection_categories-System) und /melden (DSA/DSGVO/
  Aenderung/Depublikation, verdrahtet mit den bestehenden Melde-Links an
  jeder Mitteilung; eigenstaendige Formulare = Backlog T10/T12)
- Footer beider Gerueste + Sitemap um Richtlinien/Melden ergaenzt
- Versioniertes legal_documents-System (E3) bleibt R3.1-Folgeausbau

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:53:06 +00:00
136ecc8f43 R3.2: Kontakt-/Ueber-uns-Routen zurueckgebaut, Spoke-Footer bereinigt
- GET kontakt/ueber-uns -> 301 auf die Ausgaben-Startseite;
  kontakt.store-POST bleibt fuer das Hub-Kontakt-Modal
- Legacy-Aliases (contact/kontakt/ueber-uns) buendeln auf die Startseite
- Spoke-Footer: Kontakt als mailto (Betreiber-Fallback bis R2.1),
  Ueber-uns-Link raus, Selfservice-Kuendigungslink ergaenzt
- Sitemap-Konstanten bereinigt, Tests angepasst

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:36:13 +00:00
c2d22c6d65 SEO: host-aware sitemap.xml + dynamische robots.txt je Domain
Sitemap pro Host (1h-Cache): Hub listet Landing + Rechts-/Info-Seiten,
Spokes zusaetzlich Kategorien (lokalisierte Slugs), oeffentliche
Newsrooms und alle im Archiv sichtbaren PMs des Portals (explizit ohne
PortalScope, forPortalValues). Suche und Marketing-Platzhalterseiten
bleiben draussen. Statische public/robots.txt entfernt (haette die
Route verschattet); robots.txt jetzt dynamisch mit Sitemap-Verweis
und Disallow fuer /admin, /login, /register, Suche.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:34:12 +00:00
8b8f2991c6 Recht: Kuendigungsbutton nach Par. 312k BGB - Backend + Admin-Queue
Oeffentliche Bestaetigungsseite /de/kuendigung ohne Login: Art der
Kuendigung (Grund nur bei ausserordentlich), Zuordnungsdaten,
Beendigungszeitpunkt; Honeypot + Rate-Limit wie beim Widerruf.
Sofortige automatische Zugangsbestaetigung per E-Mail (Par. 312k
Abs. 4 BGB) + Betreiber-Mail. Kuendigungen laufen in der
Recht-&-Compliance-Queue unter den Widerrufen auf (K-Vorgaenge,
eigener Filter, Erledigt/Wieder-oeffnen, vierte KPI-Karte).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 11:32:43 +00:00
4ea3baeb27 Recht: Online-Widerruf ohne Login (gesetzlicher Widerrufsbutton) - Backend
POST /widerruf (host-agnostisch, throttled, Honeypot, eigener
withdrawal-Error-Bag): persistiert nur Zuordnungsdaten (Name, E-Mail,
optionale Bestellnummer - KEIN Grund, darf nicht verlangt werden) und
versendet sofort die gesetzlich vorgeschriebene automatische
Eingangsbestaetigung mit Zeitstempel an den Verbraucher plus eine
interne Benachrichtigung an die Brand-Kontaktadresse des Hosts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 10:03:37 +00:00
603ca7378a Kontakt: Hub-Domain-Anfragen gehen an die pressekonto-Brand-Adresse
Der WebPortalContext kennt nur die beiden Spokes - Anfragen von
pressekonto.test waeren sonst bei businessportal24 gelandet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 09:52:29 +00:00
a4ae82c3af Fix: legal-request-Honeypot nickt Bots wieder still ab
Die size:0-Regeln liefen vor dem Honeypot-Zweig: ein gefuelltes Feld
produzierte einen Validierungsfehler (verraet die Falle), der stille
done()-Pfad war tot. Honeypot-Check jetzt vor der Validierung; Test
schaerft das Soll (kein Fehler, normaler Erfolgs-Redirect).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 08:14:52 +00:00
229ac110f3 R2.1: Kontaktformular-Backend (Controller + Mailable + Brand-Empfaenger)
POST kontakt mit Validierung, Honeypot (vor der Validierung, stilles
Abnicken), IP-Rate-Limit (5/h) + throttle:10,1. Versand an
brand.contact_email (neue CONTACT_EMAIL_*-Keys, [EXTERN: echte
Adressen]), Fallback MAIL_FROM_ADDRESS; Reply-To = Absender.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 08:14:52 +00:00
241d67c62a Optimierung 4.3/4.4 (Home): Page-Data-Service + Invokable Controller
Startseiten-Datenaufbereitung ($webHomeData) in HomePageData; die Edition-Home-
Route nutzt Web\HomeController (pressekonto-Redirect + Portal->View-Mapping).
Damit ist routes/web.php von ~1080 auf ~230 Zeilen geschrumpft; alle
datengetriebenen Seiten laufen jetzt ueber schlanke Invokable Controller +
Page-Data-Services. Verhalten unveraendert, 92 Web-Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:14:38 +00:00
f3aa27d154 Optimierung 4.3/4.4 (Release-Detail): Page-Data-Service + Invokable Controller
Detailseite einer Pressemitteilung (inkl. Hit-Zaehler) in ReleaseDetailPageData
+ Web\ReleaseDetailController. Verhalten unveraendert, 92 Web-Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:11:35 +00:00
e19a209de3 Optimierung 4.3/4.4 (Kategorie): Page-Data-Service + Invokable Controller
Kategorie-/Branchenseite in CategoryPageData + Web\CategoryController.
Verhalten unveraendert, 92 Web-Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:09:58 +00:00
f17e9a96bc Optimierung 4.3/4.4 (Newsroom-Detail): Page-Data-Service + Invokable Controller
Newsroom-Markenseite in NewsroomDetailPageData + Web\NewsroomDetailController.
Controller-Signatur fuehrt {edition} vor {slug} (positionsgebundene
Route-Parameter-Aufloesung). Verhalten unveraendert, 92 Web-Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:08:21 +00:00
e36f91704a Optimierung 4.3/4.4 (Newsrooms): Page-Data-Service + Invokable Controller
Newsrooms-Verzeichnis-Closure in NewsroomsPageData + Web\NewsroomsController
verschoben. Verhalten unveraendert, 92 Web-Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:04:59 +00:00
debcf81adf Optimierung 4.3/4.4 (Veroeffentlichen): Page-Data-Service + Invokable Controller
Veroeffentlichen-Closure in VeroeffentlichenPageData + Web\VeroeffentlichenController
verschoben. Verhalten unveraendert, 92 Web-Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:03:34 +00:00
01f3f7f8c7 Optimierung 4.3/4.4 (Suche): Page-Data-Service + Invokable Controller
Suche-Closure in SuchePageData + Web\SucheController verschoben. Verhalten
unveraendert, 92 Web-Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:01:18 +00:00
3f68052276 Optimierung 4.3/4.4 (Preise): Page-Data-Service + Invokable Controller
Erste Seite des Web-Routen-Umbaus: die Preise-Closure aus routes/web.php in
PreisePageData (Datenaufbereitung) + Web\PreiseController (schlanker Invokable
Controller) verschoben. Route nutzt jetzt die Controller-Klasse. Verhalten
unveraendert, Web-Tests bleiben gruen (92).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 11:59:59 +00:00
4466858aac Optimierung 3.5: Kleinere Zentralisierungen (VAT, findByEmail, Waehrung, Provisioning)
- VatResolver::normalizeCountryCode()/isEuCountry() als Single Source of Truth;
  resolve(), VatIdValidationService und StripeCheckoutService nutzen jetzt die
  zentrale EU-Pruefung statt eigener config-Lookups.
- User::findByEmail() buendelt die LOWER(email)-Lookups (ContactAccessService,
  SocialAuthService). Bleibt bewusst case-insensitiv, da E-Mails nicht
  normalisiert gespeichert werden; der indexfreundliche Direktvergleich
  braucht erst eine Schreib-Normalisierung (offen, dokumentiert).
- config('billing.default_currency') ersetzt 8 verstreute 'EUR'/'eur'-Literale
  (Stripe-Calls via strtolower). Vereinheitlicht nebenbei die zuvor uneinheitlich
  gespeicherte SinglePurchase.currency.
- Neuer CustomerAccountProvisioner::provisionVerifiedCustomer() ist die
  gemeinsame Provisioning-Methode (verifiziert + aktiv + customer-Rolle) fuer
  ContactAccessService und SocialAuthService.
- Tests: UserFindByEmailTest, erweiterte VatResolverTest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 11:14:05 +00:00
2bf431c8ba Optimierung 3.2: API-PR-Ownership-Scope + zentrale Eager-Loads
- PressRelease::scopeOwnedByApiUser() (bewusst eng: nur user_id-Autorschaft,
  anders als das breitere scopeAccessibleBy) ersetzt die byte-gleiche
  findOwnedPressRelease-Query in PressReleaseController + ImageController.
- apiResourceRelations()-Helfer ersetzt den 5x identischen Eager-Load-Set
  (company/category.translations/images) in Index/Show/Create/Update/Reclassify.

Bewusst NICHT umgesetzt: Slug-Reimplementierung (uniqueSlug) durch
HasUniqueSlug ersetzen - der API-Fallback Str::random(8) weicht vom
Trait-slugFallback() ab (Verhaltensaenderung bei leer-slugifizierten Titeln).

Tests: neuer Scope-Test; volle Suite gruen (802 passed, 1 skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:27:59 +00:00
9ddfab62c2 Optimierung 3.1: Company::scopeAccessibleBy() zentralisiert Firmenzugriff
Die 4x identische Zugriffs-Closure (owner_user_id == me OR whereHas('users',
me)) in CompanyController (index/show), PressReleaseController::
findOwnedCompany und CustomerCompanyContext::accessibleCompanyQuery durch
einen Company::scopeAccessibleBy() ersetzt; das Scope-Verhalten
(withoutGlobalScopes) bleibt beim Aufrufer.

Bewusst unangetastet: User::accessibleCompanyIds() (relationenbasiert MIT
globalen Scopes - anderes Scope-Verhalten) und PressReleaseImageController
(prueft PR-Autorschaft user_id, nicht Firmenzugriff).

Tests: neuer Scope-Test; volle Suite gruen (801 passed, 1 skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:23:16 +00:00
a5f3ddd0f3 Optimierung Phase 1: Aufraeumen / toter Code
1.1 Prototyp admin/users/table entfernt: Route (war durch admin/users/{id}
    ohnehin unerreichbar) + Volt-Komponente table.blade.php geloescht,
    ADMIN_ROUTES.md aktualisiert; Regressions-Test in PanelConsolidationTest.
1.2 16 tote Legacy-Wrapper-Views unter resources/views/admin/* geloescht
    (nutzten alte Route-Parameter, nirgends via view/@include/@livewire
    gerendert); admin.dashboard + .md-Doku behalten.
1.3 Tote Helfer entfernt: EnsureUserIsAdmin redundantes loadMissing('roles'),
    ungenutzter statusColor-Key aus with() in admin PR edit/show,
    LegacyInvoicePdfController Laufzeit-Schema::hasColumn (Spalte existiert
    seit Migration) inkl. Schema-Import.

Volle Suite gruen (794 passed, 1 skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 08:27:12 +00:00
344aac0740 Wallet-Topup ueber Stripe (Credit-Pakete mit Bonus-Staffel)
End-to-end-Aufladung der Credit-Wallet, spiegelt den Einzel-PM-Fluss:

- Paket-Staffel mit Bonus in config/credits.php (10->10, 25->27, 50->55,
  100->115 Credits; price_cents netto, Stripe Tax ergaenzt USt.)
- credit_topups (Pending->Paid) + CreditTopupService: startTopup legt den
  Pending-Kauf an, fulfill() schreibt die Wallet idempotent gut (credited_at)
- StripeCheckoutService::forCreditTopup via checkoutCharge + credit_topup_id
  Metadata; ProcessStripeWebhook schreibt bei checkout.session.completed gut
- Checkout-Route /admin/me/checkout/credits/{pack} mit Rechnungsadress-Gate

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 14:56:23 +00:00
69411b4c87 Proof-PDF + Extra-PM-Verkauf ueber die Credit-Wallet (Decision-Update 2.1/2.3)
- ProofPdfService: Veroeffentlichungsnachweis 3 Credits pauschal, einmal pro
  PM (Zweitdownload kostenfrei); ProofPdfRenderer erzeugt das PDF on-demand
  aus vorhandenen PM-Daten (kein externer Renderer); GET-Download-Endpoint
  /admin/me/press-releases/{id}/nachweis hinter downloadProof-Policy + Kauf-Gate
- ExtraPmPurchaseService: tier-gestaffelter Nachkauf (19/15/12/10/8) aus der
  Wallet; verbucht als bezahlter SinglePurchase(ExtraPm) und greift damit in
  die bestehende Kontingent-/Slot-Mechanik. InsufficientCreditsException
  liefert das Mini-Checkout-Signal (required/available/shortfall)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 14:28:08 +00:00
95007da826 WS-3: Recht & Compliance – Rechts-Kern (DSGVO/Persönlichkeitsrecht/Melden + Queue)
Launch-pflichtiger Compliance-Slice: öffentliche Anfrage zu einer PM speist eine
manuelle Admin-Queue (keine KI).

- Migration legal_requests + Model + Enums (Type: dsgvo/personal_rights/report,
  Status: open/in_progress/resolved/rejected) + Factory.
- Öffentliches Formular /release/{slug}/rechtliches (LegalRequestController +
  web/legal-request.blade.php): typ-abhängiger Hinweistext (Alpine), E-Mail bei
  DSGVO/Persönlichkeitsrecht erforderlich, zwei versteckte Honeypot-Felder,
  Rate-Limit + Bremse "1 offene Anfrage pro PM/Typ". Regeltexte als Entwurf mit
  TODO für rechtliche Finalisierung markiert.
- Routen bewusst in eigener routes/legal.php (entkoppelt vom laufenden Web-Umbau),
  host-agnostisch via domains.php eingebunden.
- Admin-Bereich "Recht & Compliance": Sidebar-Nav mit Offen-Zähler, Volt-Queue
  index/show (in Bearbeitung/erledigt/abgelehnt/wieder öffnen + interne Notiz).
- Tests: je Typ, Honeypots (Dataset), Bremse, Admin-Queue + Status-Übergänge.
- Doku: Detailplan WS-3-Status + Deployment-Migrationsreihenfolge ergänzt.

Hinweis: Der "Melden"-/E&F-Button auf der PM-Detailseite (release-detail.blade.php)
wird mit dem separaten Web-Frontend-Commit verdrahtet; Ziel ist legal-request.create.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 14:20:05 +00:00
068a5a4b49 WS-6: Google-Login via Laravel Socialite
- Socialite installiert; oauth_provider/oauth_provider_id an users (Migration).
- GoogleController (redirect/callback) + SocialAuthService: De-Dup über E-Mail,
  neuer User aktiv + verifiziert + customer (Verifizierung über den Google-
  Kanal), offener Selbst-Registrierer wird onboardet, deaktivierter Account wird
  NICHT reaktiviert. Abschluss über die gemeinsame LoginRedirect-Logik
  (rollengerecht, 403-sicher).
- Routen /auth/google/redirect + /auth/google/callback (guest), "Mit Google
  anmelden/registrieren"-Buttons auf Login und Register.
- config/services.php google + .env.example-Keys; Sicherheits-/Deployment-Doku
  ergänzt (Keys, Redirect-URI, Migration).

Tests: neuer User, De-Dup bestehender User, deaktivierter Account blockiert,
unverifizierter Registrierer onboardet, fehlgeschlagener Callback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-16 10:39:19 +00:00
d98d297524 Security-Härtung Login & Magic-Link (Review 16.06.)
- Magic-Link-Versand im Login rate-limited (E-Mail+IP 3/h und IP-only 15/h);
  verhindert Mail-Fluten und das Entwerten aktiver Links.
- Inaktive (aber verifizierte) User werden beim Passwort-Login zentral
  blockiert (Auth::logout + Fehler) – sichert nur-auth/verified-Routen ab.
- Rollensicherer Login-Redirect: gemerkte intended-Admin-URLs schicken einen
  Customer nicht mehr in den 403, sondern auf das rollengerechte Ziel.
- ContactAccess prüft is_active vor jeder Mutation: deaktivierte Bestands-
  Accounts werden durch eine Anfrage weder verändert noch angemailt.
- Magic-Link-Verbrauch atomar (UPDATE … whereNull(consumed_at)) – Single-Use
  auch bei parallelen Requests.
- Sicherheits-Doku um diese Härtungen + Captcha-Empfehlung ergänzt.

Tests: Rate-Limit, intended-Admin-URL für Customer, inaktiver Login,
ContactAccess ohne Mutation inaktiver Accounts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-16 09:33:44 +00:00
94cb209a9f WS-6: E-Mail-Verifizierung, Auth-Flow-Fixes & Legacy-Rollen-Sicherheitsfix
E-Mail-Verifizierung (Entscheidung 15.06.):
- User implementiert MustVerifyEmail; Registrierung legt inaktives, rollenloses
  Konto an und leitet auf die Danke-/Notice-Seite; Registered-Event versendet die
  Verifizierungsmail. Bestätigter Link aktiviert das Konto + vergibt customer-Rolle
  (ActivateUserAfterVerification). Backfill-Migration setzt email_verified_at für
  alle Bestands-User (sonst würde die verified-Middleware ~59k aktive Legacy-User
  aussperren). Seeder-User verifiziert.

Auth-Flow-Korrekturen:
- Magic-Link-Consume: rollensicherer Redirect ohne intended() (Customer landete
  sonst per stale intended=/dashboard im 403-Admin-Bereich).
- Guest-Redirect (bootstrap/app.php) rollen-/verifizierungsbewusst statt fix
  /dashboard – schließt die 403-Sackgasse auf /login und /register.
- Logout auf der Notice-Seite via echtes POST-Formular statt Livewire-Action
  (behebt 419 beim Session-Invalidate).
- Magic-Link-Anforderung über eigenes Modal mit separater E-Mail-Eingabe.
- Unverifizierte Login-Versuche landen auf der Notice-Seite.

Sicherheitsfix Legacy-Rollen:
- UserImporter mappte Alt-Gruppe 2 (Self-Publisher) auf editor (= Admin-Zugriff).
  Mapping auf customer korrigiert; Daten-Migration stuft die 65.950 fälschlichen
  Legacy-Editoren auf customer herab. Echte admin/api-only bleiben unberührt.

Tests: Registration, EmailVerification, Authentication (Guest-Redirect),
MagicLinkLogin (Modal/Redirect/Regression), Legacy-Import (Gruppen-Mapping).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-16 08:16:41 +00:00
afcca34f91 User-Panel-Restarbeiten: PM-Guard, Profil-Rework, USt-ID-Prüfung, Buchungspflicht-Adresse
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 14:36:18 +00:00
23ac8bc7f1 Phase 9F: Tarif-Seite mit Stripe-Checkout und Billing Portal
- Buchungs-Seite zeigt das echte 4-Tier-Raster aus plans (Monat/Jahr-
  Toggle, Jahrespreis als "2 Monate gratis") mit Checkout-Buttons,
  Einzel-PM als separaten No-Abo-Block und Enterprise-Hinweis;
  Credit-Konzept-Mock entfernt (Credits folgen mit 9I bzw. Phase 2)
- Aktueller-Tarif-Panel real: Abo (Preis, Kontingent, Kündigungsstatus),
  Bestandstarif (unbegrenzt, nächste MAN-Rechnung), offene Einzelkäufe;
  Kontingent-Kachel zeigt "Unbegrenzt" bei Bestandsschutz
- "Abo verwalten" über das Stripe Billing Portal
  (me.checkout.billing-portal; Zahlungsmethode, Rechnungen, Kündigung)
- Aktive Buchungen + Verlauf aus echten Daten (Abo, Legacy-Vereinbarung,
  offene/eingelöste Einzelkäufe mit PM-Verknüpfung)
- Tests: BookingsPageTest (9 Tests), PanelConsolidationTest angepasst;
  Suite 519 passed / 4 skipped
- Doku: PHASE-9-Plan 9F ✅, Billing-Doku (Routen, Stripe Tax aktiviert),
  STATUS-ABGLEICH, Checkliste, PROGRESS

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:39:39 +00:00
c8dc99c3c8 Phase 9E (Abschluss): Checkout-Flows und Plan-Kontingent statt Quota-Stub
- Checkout-Backend: me.checkout.subscription (Tarif-Abo monatlich/jährlich)
  und me.checkout.single-pm (Einzel-PM 19 € netto, pending-Kauf mit
  Webhook-Erfüllung); StripeCheckoutService als mockbarer Stripe-Wrapper;
  Stripe Tax via Cashier::calculateTaxes() (Netto-Preise, USt-ID-Abfrage)
- Slot-Logik: Kontingent aus dem Tarif (plans.press_release_quota) plus
  bezahlte Einmalkäufe; Verbrauch bei Veröffentlichung zuerst aus dem
  Plan-Zähler, danach Einlösung des ältesten Einmalkaufs (consumed +
  PM-Verknüpfung); Grandfathered = unbegrenzt (Entscheidung 12.06.2026,
  Bestandsschutz); Stub-Spalte users.press_release_quota entfernt
- billing:sync-stripe-plans legt zusätzlich das Einzel-PM-Produkt an
  (STRIPE_PRICE_SINGLE_PM); Test-Mode-Sync gelaufen
- Buchungs-Seite: Rückmeldung nach Checkout (erfolg/abbruch/Guard-Hinweis)
- Tests: PressReleaseQuotaTest auf Plan-Semantik neu geschrieben,
  CheckoutFlowTest (8 Tests), Modal-/API-Tests angepasst; Suite 510 passed
- Doku: Billing-und-Rechnungskreise (Kontingent-Tabelle, Checkout-Routen,
  Webhook-Events, Stripe-CLI-Hinweis), PHASE-9-Plan 9E ✅, Checkliste,
  STATUS-ABGLEICH, PROGRESS

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:10:32 +00:00
4419d9ff43 Phase 9 Block 1: Gelb-Routing Direkt-Live, Slot-Verbrauch bei Veroeffentlichung, Submit-Gate
9A — Gelb geht direkt live (Entscheidung 12.06.2026):
- routeByClassification(): Gelb durchlaeuft denselben Auto-Publish-Pfad
  wie Gruen (autoPublishApproved); nur Rot wird abgelehnt
- Scheduler publiziert faellige gelbe + gruene PMs; unklassifizierte
  bleiben als Fallback in der manuellen Queue

9B — Slot-Verbrauch bei Veroeffentlichung (Decision-Update 3.2):
- Increment aus submitForReview() entfernt; publish() und
  changeStatusFromAdmin() zaehlen idempotent beim ersten
  published-Uebergang (Pruefung ueber Status-Logs); Rot kostet nichts
- Submit-Guard: Einreichen erfordert freien Slot
  (QuotaExceededException, API 422)

9C — Submit-Gate vorbereitet (Decision-Update 5.1):
- User::hasActiveBooking()-Stub hinter config/billing.php
  (enforce_booking, Default aus); Tarif-Modul ersetzt nur den Rumpf
- Einreichungs-Modal zeigt ohne Buchung einen Buchungs-Hinweis;
  Server-Guard (BookingRequiredException), API antwortet 402
- Fix: Customer-Create legte PMs bei "Zur Pruefung senden" direkt mit
  Status review an (vorbei an Blacklist/Quota/KI/Status-Log) — laeuft
  jetzt immer ueber submitForReview()

Suite: 451 passed, 4 skipped (9 neue Tests). Pint clean.
Plan: docs/PHASE-9-FLOW-UND-TARIFE-PLAN.md (Block 2 nach Review-Stopp).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 09:47:06 +00:00
a000238ca8 User Panel: Phase-8-Abschluss, Titelbild/Lizenzen/Zeitzonen und KI-Pruef-Pipeline
Phase 8 (Rest) + Umbauten vom 10./11.06.:
- Ein Titelbild pro PM (Cover 1280x580), SVG-Platzhalter-Set + Picker,
  PressReleaseCoverImage-Resolver
- Lizenz-/Rechteformular nach "Lizenztyp Bildupload" (7 Lizenztypen,
  Personen-/Sachrechte-Status, bedingte Pflichtfelder, Risikohinweise)
- Veroeffentlichungs-Box vereinfacht (Embargo aus der Form-UI entfernt),
  geplante Termine in Europe/Berlin (Speicherung UTC, DISPLAY_TIMEZONE)
- Quota-Stub (users.press_release_quota) + monatlicher Reset-Command
- Einreichungs-Modal einheitlich in Show/Create/Edit; Ghost-Buttons auf
  filled; PM-Editor-Layout responsive entkoppelt (.pr-editor-layout)

KI-Pruef-Pipeline (Phasen 1-5 des Entwicklungsplans):
- API-Haertung: status nicht mehr per API setzbar, eigene Submit-Route
  durch denselben Funnel (Blacklist, Quota, Status-Log)
- Klassifikation Rot/Gelb/Gruen asynchron (Queue classification,
  OpenAI-Treiber + deterministischer Fallback), ki_audits-Audit-Log
- Routing: Rot -> rejected + Mail, Gelb -> Review-Queue, Gruen ->
  Auto-Publish; Scheduler publiziert nur gruene faellige PMs
- Content-Score 0-100 -> Stufe (Standard/Geprueft/Hochwertig) inkl.
  Editor-Panel und Badges; Re-Klassifikation/-Score bei Aenderung
- Admin: KI-Badge + Filter, On-Demand-Pruefung mit Anbieter-Override

Suite: 442 passed, 4 skipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:30:13 +00:00
5b8bdf4182 12-05-2026 Frontend dev
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run
2026-05-12 18:32:33 +02:00
405df0a122 first commit
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
2025-10-20 17:53:02 +02:00