**Version:** Juni 2026 · **Datum:** 15.06.2026 · **Status:** Abgestimmt – in Umsetzung **Scope:** Detaillierter Umsetzungsplan für die nächste Phase. Bündelt die Launch-Anteile der beiden Decision-Updates (Phase-2/Magic-Link, Duplicate-Content) mit den offenen Punkten aus `weiteres/Merkliste.md` sowie den Rest-Zweigen Login-Flow und Code-Optimierung. --- ## 0. Ausgangslage – was bereits im Code existiert Die Kartierung der betroffenen Subsysteme hat ergeben, dass einiges bereits halb gebaut ist. Der reale Restaufwand ist dadurch kleiner als die Konzept-Dokumente vermuten lassen – vieles ist „verifizieren + scharf schalten + UI" statt „von null bauen". | Thema | Ist-Stand | |---|---| | Magic-Link | Basis vorhanden (`app/Models/MagicLink.php`, `MagicLinkGenerator`, `MagicLinkConsumeController`) – aber nur **Login registrierter User** + **Read-only-PM-Vorschau**. Pressekontakt-per-E-Mail-Zugang fehlt. | | Abo-/Bestandsschutz-Gate | **Bereits implementiert:** `User::hasActiveBooking()` zählt `grandfathered` mit; `app/Services/PressRelease/PressReleaseService.php` wirft `BookingRequiredException`. Aber hinter Launch-Schalter `billing.enforce_booking=false` deaktiviert. | | Bestandsschutz | Abgebildet als `UserPaymentOption.status='grandfathered'` + `legacy_conditions` (JSON). Einmal-Import `GrandfatherLegacySubscriptions` + täglicher MAN-Worker `GenerateManualInvoices` (04:30) existieren. | | Boost / Proof-PDF / Extra-PM | Enum-Cases existieren (`SinglePurchaseType`), aber **keine Credit-Wallet** → bleibt Phase 2. | | Canonical-Tags | Fehlen in `resources/views/web/layouts/web-master.blade.php` (`
` hat nur `meta_description`). | | „Melden"-Button | Platzhalter (`href="#"`) in `resources/views/web/release-detail.blade.php`, kein Backend. | | Google-Login / Socialite | **Nicht installiert** – Greenfield (hiermit freigegeben). | | E-Mail-Verifizierung | Fortify-Feature `emailVerification()` aktuell **nicht aktiv**; Registrierung setzt weder `is_active` noch Rolle. | --- ## 1. Verbindliche Entscheidungen (15.06.2026) 1. **Legacy-Posten bleiben unverändert.** Bestandsschutz-Posten werden **nicht** auf ein Tarif-Abo (z. B. Agentur) gemappt. Die Positionen bleiben als eigene Legacy-Konditionen erhalten, so wie sie sind. 2. **Registrierung mit Pflicht-Verifizierung.** Neu registrierte Benutzer müssen sich **einmalig per E-Mail verifizieren**. Erst nach Verifizierung: `customer`-Rolle + `is_active=true`. **Ausnahme:** Magic-Link, Google-Login & vergleichbare Wege – dort findet die Verifizierung über den Login-Kanal selbst statt (keine zusätzliche E-Mail-Verifizierung nötig). 3. **Laravel Socialite freigegeben** als neue Abhängigkeit (für Google-Login, später ggf. weitere Provider). --- ## 2. Bewusst NICHT in dieser Phase (Phase 2, credit-abhängig) Der Großteil des Phase-2/Magic-Link-Dokuments hängt an der **Credit-Wallet**, die noch nicht existiert. Diese Punkte werden bewusst zurückgestellt, bis die Wallet gebaut ist: - Pfade **C** (Inhaltliche Korrektur), **D** (Update/Ergänzung), **G** (Depublizieren) - **Boost** (Platzierung), **Veröffentlichungsnachweis/PDF**, **Extra-PM** (Kontingent-Nachkauf) - **Prüfzähler** & höheres Prüfkontingent, Vorab-Prüfung/Redigieren Vom **Duplicate-Content-Dokument** ist die Entscheidung ohnehin „nichts bauen"; einziger umsetzbarer Launch-Punkt dort: **Canonical-Tags** (siehe WS-1). Hash-/Shingle-Vergleich bleibt Phase-3-Option. --- ## 3. Arbeitspakete ### WS-1 — SEO Canonical-Tags (Duplicate-Doc §5) · klein, isoliert **Scope:** Jede öffentliche PM bekommt ihre kanonische URL; pro Portal getrennt (kein Cross-Portal → kein System-Duplicate). - `` + OpenGraph-Tags (og:title/description/image) in `web-master.blade.php` via `@yield`/`@stack`. - `release-detail.blade.php` setzt die kanonische URL der PM; Listen-/Übersichtsseiten Self-Canonical. - **Tests:** Feature-Test prüft `rel="canonical"` mit korrekter URL auf veröffentlichter PM. - **Aufwand:** ~½ Tag. Keine Migration, kein Risiko. ### WS-2 — Magic-Link-Pressekontaktzugang (Phase-2-Doc §3.1) · Fundament **Scope:** Zugangsbrücke „eine Verwaltung, zwei Eintrittswege" für unregistrierte Pressekontakte. - Neuer Magic-Link-`purpose` = `contact_access` im bestehenden `MagicLinkGenerator` (TTL 30 Min). - Dezenter Link auf jeder öffentlichen PM: „Sie sind als Pressekontakt hinterlegt? → verwalten". - E-Mail-Eingabe + Captcha; **enumeration-sichere identische Antwort** unabhängig vom Match. - Bei Match → Mail mit Token → authentifizierte Session, die alle PMs zu dieser E-Mail listet (`Contact::where('email',…)->pressReleases()`, firmen-/jahresübergreifend). - Rate-Limit pro E-Mail/IP; Audit-Log (IP/User-Agent) je Aktion. - **Account-Konvertierung:** „Permanenten Account anlegen" aus der Magic-Link-Session (Passwort vergeben) → Verifizierung gilt über den Magic-Link-Kanal als erfolgt (siehe Entscheidung 2). - **Tests:** Token-Lifecycle, Enumeration-Sicherheit, abgelaufener/verbrauchter Token, PM-Liste nur eigene E-Mail. - **Aufwand:** ~2–3 Tage. **Fundament für WS-3.** ### WS-3 — Änderungs-/Lösch-Pfade A · B · E · F (Phase-2-Doc §3.2/3.3) · Launch-Pflicht **Status (16.06.): WS-3 launch-fertig.** - **E · F + Melden + Admin-Queue – erledigt** (Commit `95007da`): Tabelle `legal_requests`, öffentliches Formular `/release/{slug}/rechtliches` (DSGVO/Persönlichkeitsrecht/Meldung, enumeration-neutral, zwei Honeypots, Rate-Limit, Bremse „1 offene Anfrage/PM+Typ", typabhängiger Hinweistext), „Melden"-/E&F-Einstieg auf der PM-Detailseite, Admin-Bereich „Recht & Compliance" (Nav-Badge mit Offen-Zähler, Queue index/show mit erledigt/abgelehnt/wieder öffnen + interner Notiz). Routen entkoppelt in `routes/legal.php`. Tests grün. **Hinweis:** Regeltexte je Typ sind Entwurf (TODO im Formular) – vor Launch mit Recht/Legal finalisieren. - **B — Kontaktdaten ändern – abgedeckt** (über WS-2-Firmen-Scope): `responsible`-Pressekontakte bearbeiten die Kontakt-Datensätze im Firmen-Bereich; die öffentliche PM-Detailseite lädt Kontakte aus der Relation, daher spiegeln sich Änderungen auf veröffentlichten PMs. Keine separate Hintergrund-Versionierung (direkte Übernahme). - **A — Tippfehler/Grammatik – bewusst zurückgestellt (Phase 2 / Pfad C).** Veröffentlichte PMs sind im Kundenbereich nicht editierbar (nur `draft`/`rejected`). A würde veröffentlichte Inhalte automatisch ändern (Integritätsrisiko), ist nicht launch-blockierend und überlappt mit Pfad C. Entscheidung 16.06.: nach Phase 2. Alle hinter WS-2 **oder** regulärem Login. - **B — Kontaktdaten ändern** (einfachster Pfad zuerst): Formular, direkt übernommen, Versionierung im Hintergrund. Kostenfrei. - **A — Tippfehler/Grammatik:** Inline-Editor mit **Levenshtein-Diff** (kein KI-Call). Kleine Änderung ohne Zahlen/Namen → automatisch; Graubereich → Hinweis „bitte als Korrektur (Pfad C, Phase 2) einreichen". Bremse: 1 Einreichung/PM/24 h + Account-Tages-Cap. - **E — DSGVO-Anonymisierung** & **F — Persönlichkeitsrecht:** **rechtlich zwingend ab Launch.** Formular → Eintrag in neuer Tabelle `legal_requests` → **Admin-Queue** (keine KI). Bremse: „1 offene Anfrage pro PM" (nicht hart gesperrt). - **Öffentlicher „Melden"-Button** (§3.5) verdrahten → speist dieselbe Queue wie F. - **Neuer Admin-Bereich „Recht & Compliance":** Nav in `components/layouts/app/sidebar.blade.php`, Routen in `routes/admin.php`, Volt-Komponenten unter `livewire/admin/legal-requests/` nach Muster `admin/contacts/index`. - **Migration:** `legal_requests` (press_release_id, type[dsgvo/personal_rights/report], status, payload, requester_email, resolved_at, …). - **Tests:** je Pfad ein Feature-Test; „1 offene Anfrage/PM"-Bremse; Admin-Queue-Sichtbarkeit; Melden→F-Queue. - **Aufwand:** ~4–5 Tage (E/F + Queue der Großteil). ### WS-4 — Bestandsschutz-Abrechnung & -Übersicht (Merkliste 1·2·3·5) **Scope:** Vorhandene Mechanik (`GrandfatherLegacySubscriptions`, `GenerateManualInvoices`) verifizieren + Lücken schließen. **Legacy-Posten bleiben unverändert (Entscheidung 1) – kein Tarif-Mapping.** **Status (16.06.):** - **M1 (portalübergreifend) – verifiziert:** `hasActiveBooking()` / `duePaymentOptions()` filtern nicht nach `legacy_portal`; Beweis-Test ergänzt (grandfathered Posten zählt unabhängig vom aktiven Portal-Kontext). - **M5 (Import + 04:30-Worker) – verifiziert:** `GenerateManualInvoices` läuft täglich 04:30 (`routes/console.php`), berücksichtigt Active + Grandfathered ohne Stripe. - **M2 (gemeinsam abrechnen, keine Einzelkündigung) – umgesetzt (Entscheidung 16.06.: getrennte Rechnungen, gekoppelte Kündigung):** kein Rechnungs-Schema-Umbau; jede Kondition behält ihre eigene MAN-Rechnung. Neuer `LegacySubscriptionService::cancelBundleFor()` kündigt das gesamte Bestandsschutz-Bündel eines Kunden **gemeinsam** – es gibt bewusst keine Einzelkündigung. - **M3 (Admin-Sichtbarkeit) – umgesetzt:** Admin-Payments-Ansicht zeigt eine eigene „Bestandsschutz-Posten"-Tabelle (Badge, Portal, Netto, nächste Fälligkeit, „Bündel (N)"-Indikator) inkl. Bündel-Kündigung mit Bestätigung. - Tests: `tests/Feature/Billing/LegacyBundleTest.php` (M1/M2/M3). **Offen optional:** kombinierte Sammel-Rechnung (eine Rechnung mit Positionen) wurde bewusst nicht gebaut. - **M1 — Beide Portale ohne Unterscheidung:** Bestandsschutz gilt portalübergreifend, auch wenn nur für ein Portal gebucht → sicherstellen, dass `legacy_portal` im Import **kein** Filter ist. - **M2 — Zwei Posten zusammen abrechnen, nicht einzeln kündbar:** Zwei Legacy-Posten desselben Kunden (je Portal, z. B. connektar 2×1000 € netto) werden gemeinsam abgerechnet und gelten zusammen (Gesamtsumme 2000 € netto bleibt). Die einzelnen Positionen bleiben als eigene Legacy-Konditionen erhalten – **keine** Zusammenführung zu einem Tarif, **keine** Einzelkündigung. - **M5 — Einmal-Import + täglicher Worker:** Verifizieren, dass alle aktiven Abrechnungen erfasst werden und der 04:30-Worker fällige Rechnungen korrekt erzeugt; ggf. Gesamtübersicht ergänzen. - **M3 — Ansicht deutlicher:** Im Admin-Payments-Bereich Bestandsschutz-Posten optisch klar kennzeichnen/zuordnen (Badge „Bestandsschutz", Portal-Zuordnung, gebündelte Posten sichtbar). - **Tests:** gemeinsame Abrechnung zweier Posten; portalübergreifende Geltung; Worker erzeugt Rechnung zum Periodenende. - **Aufwand:** ~2–3 Tage. Parallel zu WS-1/2 möglich. ### WS-5 — Abo-/Bestandsschutz-Gate scharf schalten (Merkliste 6) — ✅ erledigt 16.06.2026 **Scope:** „User ohne aktives Abo oder Bestandsschutz dürfen keine PM schreiben." - Mechanik existiert (`hasActiveBooking()` inkl. `grandfathered`, `BookingRequiredException`) und ist mit `enforce_booking=true` voll getestet: - **Backend-Gate:** `PressReleaseService::submitForReview()` wirft `BookingRequiredException`/`QuotaExceededException`. - **API:** `POST /api/v1/press-releases/{id}/submit` → `402` (keine Buchung) bzw. `422` (Kontingent erschöpft). - **Block-UX am Einreichpunkt:** `press-release-submit-modal.blade.php` zeigt ohne Buchung statt des Prüf-Flows den Hinweis „Buchung erforderlich" + Button zur Buchungsseite; alle Volt-Caller (show/edit/create/index) fangen die Exceptions und zeigen einen Toast. - **Proaktiver Hinweis:** Kunden-Dashboard zeigt bei scharfem Gate ohne aktive Buchung einen Banner „Buchung erforderlich, um zu veröffentlichen" → `me.bookings.index`. Bestandskunden (grandfathered) sehen ihn nicht. - **Launch-Flip:** Kein Code-Default ändern — der Schalter bleibt in `config/billing.php` auf `false` (Dev-Default). Scharfschalten ausschließlich per Prod-Env `BILLING_ENFORCE_BOOKING=true` (→ WS-7-Checkliste). - **Tests:** `HasActiveBookingTest`, `PressReleaseSubmitApiTest` (402/422), `DashboardTest` (Banner an/aus + grandfathered-Ausnahme), `LegacyBundleTest` (grandfathered zählt portalübergreifend). - **Aufwand:** erledigt. **Abhängigkeit:** WS-4 (erledigt). ### WS-6 — Login/Registrierung: Flow + E-Mail-Verifizierung + Google-Login (Merkliste 7 + Zweig 3) — ✅ erledigt 17.06.2026 **Scope nach Entscheidung 2 & 3 — vollständig umgesetzt (Audit 17.06., 65 Auth-Tests grün):** - **E-Mail-Verifizierung scharf:** `User implements MustVerifyEmail`; `verified`-Middleware greift real auf den Panel-Routen (`routes/admin.php`, `routes/customer.php`). Eigener Volt-Flow statt Fortify-Built-in (`Features::emailVerification()` bewusst aus, wie `registration()`/`resetPasswords()`): Notice-Volt + `VerifyEmailController` + Resend-Route. Lockout der Bestands-User durch Backfill-Migration verhindert. - **Rolle + Aktivierung erst nach Bestätigung:** `CreateNewUser` legt inaktiv & rollenlos an; `App\Listeners\ActivateUserAfterVerification` (auf `Verified`-Event) setzt `is_active=true` + `customer`. - **Ausnahme-Kanäle:** Magic-Link & Google gelten als verifiziert → `SocialAuthService`/Magic-Link setzen `email_verified_at` + Rolle direkt. - **Google-Login:** `laravel/socialite ^5.27`, `config/services.php`, `GoogleController` (redirect/callback), `oauth_provider`/`oauth_provider_id`-Migration, De-Dup über E-Mail. - **Flow-Audit:** Login (2FA-Bypass behoben), Passwort-Reset, Verify-Email, 2FA-Challenge, Magic-Link – alle als Volt-Komponenten, gehärtet (Security-Review 16.06.). - **Tests:** `tests/Feature/Auth/` (10 Dateien): Registrierung→Verifizierung→Aktivierung, unverifiziert blockiert, Socialite gemockt, Account-Verknüpfung, Magic-Link/Google ohne E-Mail-Schritt. - **Operativ offen → WS-7:** Google-Keys + Redirect-URI in der Prod-`.env`. - **Doku:** [Sicherheit & Deployment-Hinweise (Auth, Rollen, Verifizierung).md](Sicherheit%20&%20Deployment-Hinweise%20(Auth,%20Rollen,%20Verifizierung).md). ### WS-7 — Live-Deployment-Checkliste (Merkliste 4) — ✅ erledigt 17.06.2026 - Kompakte, abarbeitbare Go-Live-Checkliste erstellt: [Live-Deployment-Checkliste (WS-7).md](Live-Deployment-Checkliste%20(WS-7).md). Deckt ab: Live-`.env` (Domains, MySQL + Legacy-DBs, Session/Cookies, `BILLING_ENFORCE_BOOKING=true`, `BILLING_OWN_VAT_ID`, Stripe-Live-Keys, Google-/OpenAI-Keys, echter Mailer), Code/Assets-Build, Migrationen (Reihenfolge), einmalige Setups (`billing:sync-stripe-plans`, `legacy:grandfather-subscriptions` für `net_cents`, `domains:generate-favicons`), Stripe-Webhook, Cache-Warmup, Scheduler-Cron + Queue-Worker, Post-Deploy-Kontrollen. - **Aufwand:** erledigt (reine Doku). ### WS-8 — Code-Optimierung (Zweig 4) - Nach Abschluss der Features: gezielte Optimierung (N+1 in Listen, doppelte Query-Logik, Service-Extraktion). Bewusst zuletzt, wenn der Funktionsumfang steht. --- ## 4. Empfohlene Reihenfolge ``` 1. WS-1 Canonical-Tags (Quick-Win, isoliert) 2. WS-2 Magic-Link-Kontaktzugang (Fundament) 3. WS-3 Pfade B → A → E/F + Queue (Launch-Pflicht, auf WS-2) 4. WS-4 Bestandsschutz-Abrechnung (parallel möglich) 5. WS-5 Abo-Gate scharf schalten (nach WS-4) 6. WS-6 Login-Flow + E-Mail-Verifizierung + Google 7. WS-7 Deployment-Checkliste 8. WS-8 Code-Optimierung (zuletzt) ── Phase 2 (später): Wallet, C/D/G, Boost, Proof-PDF, Extra-PM, Prüfzähler ── ``` --- ## 5. Anti-Zombie-Check - ✅ Phase-2-Funktionen (Wallet-abhängig) bewusst zurückgestellt – nichts auf Vorrat gebaut. - ✅ Legacy-Posten bleiben unangetastet – kein riskantes Tarif-Mapping bestehender Konditionen. - ✅ Rechtlich zwingende Pfade E/F ab Launch erreichbar, kostenfrei, nie hart gesperrt. - ✅ Vorhandene Mechanik (Abo-Gate, Bestandsschutz-Worker, Magic-Link-Basis) wird verifiziert/erweitert statt neu gebaut. - ✅ Duplicate-Checking nicht gebaut – nur Canonical-Hygiene, wie im Decision-Update entschieden.