Die zu messende Strecke führt von einer Mail über presseecho.de per Magic Link auf pressekonto.com und endet in einer Buchung — drei Wechsel, zwei Domains. Ein cookiefreies Analytics-Tool verliert die Sitzung genau dort, wo die interessante Konversion passiert. Claim, Registrierung und Buchung sind außerdem keine Seitenaufrufe, sondern Zustandsänderungen in dieser Datenbank, bei denen die Identität ohnehin bekannt ist. Deshalb eigene Ereignisdaten statt Umami; Umami behält Seitenaufrufe und Suchbegriffe. `conversion_events` hält Ereignistyp, Herkunft sowie Firmen- und Meldungsbezug — keine Personen, keine Klartextadressen, keine IP. Der rote Faden durch den Trichter ist die Firma; für die Frage "an welcher Stufe bricht es ab" reicht das vollständig. Der Kern ist die Herkunft auf dem Token (neue Spalte magic_links.source). Ohne sie weiß die Auswertung am Ende, dass jemand gebucht hat, aber nicht, ob er aus einer Ablehnungsmail kam. Eine Session überlebt den Domainwechsel nicht, das Token schon. Die Mail-Links tragen zusätzlich `?src=` und `&pr=`; die Meldungs-ID ist fälschbar, schaltet aber nichts frei und verfälscht im schlimmsten Fall nur unsere eigene Statistik. Mit dem Magic-Link-Zugang aus 1b.2 entfällt sie. Fünf Stufen statt gleichrangiger Punkte, jede mit ihrer Diagnose am Enum: Vier beliebige Konversionspunkte sagen nicht, WO es hakt. Geschrieben wird an Stufe 1 (globale Middleware — die Mail-Links zeigen auf Hub, Spokes und öffentliche Seiten, eine vergessene Route wäre eine unsichtbare Lücke), Stufe 2 (Magic-Link-Einlösung), Stufe 4 (Registrierung) und Stufe 5 (beide Zahlungspfade, je Firma dedupliziert — eine zweite Buchung ist keine erste). Stufe 3 ist definiert und wird ausgewertet; ihre Schreibstelle kommt mit der Profilpflege aus 1b.3. Für alle Schreibstellen gilt: Fehler werden geschluckt. Die Messung begleitet einen Vorgang, sie ist nicht der Vorgang — eine kaputte Statistik darf keine Anmeldung und erst recht keine Buchung scheitern lassen. `conversion:funnel` zählt je Stufe die eindeutigen Firmen, nicht die Ereignisse: Ein doppelter Klick ist kein doppelter Interessent, und nur mit derselben Einheit auf allen Stufen ist ein Übergang überhaupt eine Quote. Ereignisse ohne Firmenbezug stehen daneben statt im Nenner. Aufbewahrung 395 Tage, zusammen mit der Tabelle festgelegt statt nachgetragen, täglich im Scheduler bereinigt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
235 lines
8 KiB
Text
235 lines
8 KiB
Text
APP_NAME=Pressekonto
|
||
APP_ENV=local
|
||
APP_KEY=
|
||
APP_DEBUG=true
|
||
APP_URL=https://pressekonto.test
|
||
|
||
APP_LOCALE=de
|
||
APP_FALLBACK_LOCALE=de
|
||
APP_FAKER_LOCALE=en_US
|
||
|
||
APP_MAINTENANCE_DRIVER=file
|
||
# APP_MAINTENANCE_STORE=database
|
||
|
||
# HTTP Basic Auth Gate (Staging-Schutz). Nur erzwungen, wenn aktiviert
|
||
# UND BASIC_AUTH_USER/BASIC_AUTH_PASSWORD gesetzt sind.
|
||
BASIC_AUTH_ENABLED=true
|
||
BASIC_AUTH_USER=
|
||
BASIC_AUTH_PASSWORD=
|
||
|
||
PHP_CLI_SERVER_WORKERS=4
|
||
|
||
BCRYPT_ROUNDS=12
|
||
|
||
LOG_CHANNEL=stack
|
||
# Prod: auf `daily` stellen — der `single`-Channel wächst sonst unbegrenzt.
|
||
# Der daily-Channel rotiert mit LOG_DAILY_DAYS (Default 14, siehe Runbook).
|
||
LOG_STACK=single
|
||
LOG_DEPRECATIONS_CHANNEL=null
|
||
LOG_LEVEL=debug
|
||
|
||
# Betriebs-Alerts (R5.3): fehlgeschlagene Scheduler-Tasks/Queue-Jobs werden als
|
||
# `critical` geloggt und – wenn aktiviert – zusätzlich an MONITORING_ALERT_EMAIL
|
||
# gemeldet (Fallback: LEGAL_CONTACT_EMAIL). In Prod echten Mailer voraussetzen.
|
||
MONITORING_ALERTS_ENABLED=true
|
||
MONITORING_ALERT_EMAIL=
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Multi-Domain-Setup (config/domains.php)
|
||
# Hub: pressekonto (Portal-Backend + Hub-Landing) — Spokes: presseecho,
|
||
# businessportal24. Prod: .test durch echte Domains ersetzen
|
||
# (pressekonto.de, presseecho.de, businessportal24.com).
|
||
# ---------------------------------------------------------------------------
|
||
APP_PROTOCOL="https://"
|
||
|
||
APP_PORTAL_NAME="pressekonto.test"
|
||
APP_PORTAL_URL="https://pressekonto.test"
|
||
APP_PORTAL_PRIMARY="#526266"
|
||
APP_PORTAL_SECONDARY="#82a0a7"
|
||
# APP_PORTAL_ASSET_URL=
|
||
|
||
APP_PRESSEECHO_NAME=presseecho.test
|
||
APP_PRESSEECHO_URL=https://presseecho.test
|
||
APP_PRESSEECHO_PRIMARY="#345636"
|
||
APP_PRESSEECHO_SECONDARY="#6b8f71"
|
||
|
||
APP_BUSINESSPORTAL_NAME=businessportal24.test
|
||
APP_BUSINESSPORTAL_URL=https://businessportal24.test
|
||
APP_BUSINESSPORTAL_PRIMARY="#cf3628"
|
||
APP_BUSINESSPORTAL_SECONDARY="#f0834a"
|
||
|
||
# Sanctum: Hosts, die stateful (Session-Cookie) auf die API zugreifen dürfen.
|
||
# Prod: echte Domains kommasepariert eintragen.
|
||
SANCTUM_STATEFUL_DOMAINS=pressekonto.test,presseecho.test,businessportal24.test
|
||
|
||
DB_CONNECTION=sqlite
|
||
# DB_HOST=127.0.0.1
|
||
# DB_PORT=3306
|
||
# DB_DATABASE=laravel
|
||
# DB_USERNAME=root
|
||
# DB_PASSWORD=
|
||
|
||
# Wartezeit auf Tabellen-Sperren in Sekunden (siehe config/database.php).
|
||
# Migrationen auf großen Tabellen ggf. mit DB_LOCK_WAIT_TIMEOUT=300 starten.
|
||
DB_LOCK_WAIT_TIMEOUT=10
|
||
|
||
# Aufbewahrung des PM-Ereignis-Logs in Tagen (siehe config/press_releases.php).
|
||
# Bestimmt zugleich die Tiefe der Verlaufsanzeige. 395 = 13 Monate.
|
||
PRESS_RELEASE_EVENTS_RETENTION_DAYS=395
|
||
|
||
# Aufbewahrung der Konversions-Ereignisse in Tagen (siehe config/conversion.php).
|
||
# Bewusst derselbe Horizont wie oben. 395 = 13 Monate.
|
||
CONVERSION_EVENTS_RETENTION_DAYS=395
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Legacy-Datenbanken (Alt-Systeme, read-only für Migration/Abgleich).
|
||
# Die businessportal-Verbindung ist ein Snapshot-Dump, nicht live.
|
||
# ---------------------------------------------------------------------------
|
||
DB_HOST_BUSINESSPORTAL=127.0.0.1
|
||
DB_DATABASE_BUSINESSPORTAL=
|
||
DB_USERNAME_BUSINESSPORTAL=
|
||
DB_PASSWORD_BUSINESSPORTAL=
|
||
|
||
DB_HOST_PRESSEECHO=127.0.0.1
|
||
DB_DATABASE_PRESSEECHO=
|
||
DB_USERNAME_PRESSEECHO=
|
||
DB_PASSWORD_PRESSEECHO=
|
||
|
||
SESSION_DRIVER=redis
|
||
SESSION_LIFETIME=120
|
||
SESSION_ENCRYPT=false
|
||
SESSION_PATH=/
|
||
SESSION_DOMAIN=null
|
||
# Prod: true setzen (Cookies nur über HTTPS).
|
||
# SESSION_SECURE_COOKIE=true
|
||
|
||
BROADCAST_CONNECTION=log
|
||
FILESYSTEM_DISK=local
|
||
QUEUE_CONNECTION=database
|
||
|
||
CACHE_STORE=redis
|
||
# CACHE_PREFIX=
|
||
|
||
MEMCACHED_HOST=127.0.0.1
|
||
|
||
REDIS_CLIENT=phpredis
|
||
REDIS_HOST=127.0.0.1
|
||
REDIS_PASSWORD=null
|
||
REDIS_PORT=6379
|
||
|
||
# Prod: echten Mailer konfigurieren — Default `log` versendet NICHTS.
|
||
MAIL_MAILER=log
|
||
MAIL_SCHEME=null
|
||
MAIL_HOST=127.0.0.1
|
||
MAIL_PORT=2525
|
||
MAIL_USERNAME=null
|
||
MAIL_PASSWORD=null
|
||
MAIL_FROM_ADDRESS="hello@example.com"
|
||
MAIL_FROM_NAME="${APP_NAME}"
|
||
|
||
AWS_ACCESS_KEY_ID=
|
||
AWS_SECRET_ACCESS_KEY=
|
||
AWS_DEFAULT_REGION=us-east-1
|
||
AWS_BUCKET=
|
||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||
|
||
VITE_APP_NAME="${APP_NAME}"
|
||
# Lokaler Vite-Dev-Server (dual-port Setup, siehe VITE-SETUP.md).
|
||
VITE_DEV_SERVER_URL=https://assets.pressekonto.test
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Stripe / Billing (Laravel Cashier)
|
||
# Prod: Live-Keys + Live-Preis-IDs eintragen [EXTERN: R1.2].
|
||
# ---------------------------------------------------------------------------
|
||
STRIPE_KEY=
|
||
STRIPE_SECRET=
|
||
STRIPE_WEBHOOK_SECRET=
|
||
# Stripe-Preis-ID des Einzelkaufs (eine PM, 19 €).
|
||
STRIPE_PRICE_SINGLE_PM=
|
||
CASHIER_CURRENCY=eur
|
||
CASHIER_CURRENCY_LOCALE=de_DE
|
||
|
||
# Umsatzsteuer/Rechnungsangaben.
|
||
# BILLING_VAT_RATE=0.19
|
||
# BILLING_DEFAULT_CURRENCY=EUR
|
||
# BILLING_MANUAL_DUE_DAYS=14
|
||
BILLING_OWN_VAT_ID=
|
||
|
||
# Abo-/Bestandsschutz-Gate (WS-5). Default (auch ohne Env-Key) ist true:
|
||
# Einreichen zur Prüfung erfordert ein aktives Abo, einen bezahlten Einzelkauf
|
||
# oder Bestandsschutz (grandfathered) — siehe User::hasActiveBooking().
|
||
# Nur für lokale Entwicklung bewusst auf false setzen (Gate offen für alle).
|
||
BILLING_ENFORCE_BOOKING=true
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# KI: Klassifikation (Red-Flag-Gate) + Content-Score (config/scoring.php).
|
||
# Ohne OPENAI_API_KEY laufen die Jobs nicht — Queue-Worker erforderlich.
|
||
# ---------------------------------------------------------------------------
|
||
OPENAI_API_KEY=
|
||
# OPENAI_API_URL=
|
||
# OPENAI_MODEL=
|
||
# OPENAI_TIMEOUT=30
|
||
|
||
# CLASSIFICATION_PROVIDER=openai
|
||
# CLASSIFICATION_MODEL=
|
||
# CLASSIFICATION_TIMEOUT=15
|
||
# CLASSIFICATION_GREEN_DELAY=0
|
||
# CLASSIFICATION_YELLOW_MANUAL=true
|
||
|
||
# CONTENT_SCORE_PROVIDER=openai
|
||
# CONTENT_SCORE_MODEL=
|
||
# CONTENT_SCORE_TIMEOUT=30
|
||
# CONTENT_SCORE_HOCHWERTIG=80
|
||
# CONTENT_SCORE_GEPRUEFT=60
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Newsletter-Sync (config/newsletter.php). Provider `placeholder` bindet den
|
||
# Null-Client; enabled=true mit anderem Provider wirft bewusst, bis eine
|
||
# echte Anbindung implementiert ist.
|
||
# ---------------------------------------------------------------------------
|
||
NEWSLETTER_SYNC_ENABLED=false
|
||
NEWSLETTER_SYNC_PROVIDER=placeholder
|
||
NEWSLETTER_SYNC_ENDPOINT=
|
||
NEWSLETTER_SYNC_API_KEY=
|
||
# NEWSLETTER_SYNC_TIMEOUT=10
|
||
|
||
# Drosselung interner Newsletter-Kampagnen: Wellen à batch_size Mails im
|
||
# Abstand von batch_interval_minutes (SMTP-Limit/Spam-Schutz).
|
||
# NEWSLETTER_CAMPAIGN_BATCH_SIZE=50
|
||
# NEWSLETTER_CAMPAIGN_BATCH_INTERVAL_MINUTES=15
|
||
|
||
# Google-Login (Laravel Socialite). Die Redirect-URI muss in der Google Cloud
|
||
# Console EXAKT der Callback-URL der Portal-Domain entsprechen (Login läuft auf
|
||
# dem Portal-Host, nicht zwingend APP_URL). Prod: https://pressekonto.com/...
|
||
# Hinweis: Google akzeptiert keine .test-Domains – lokal nur localhost oder ein
|
||
# https-Tunnel; der echte Round-Trip wird auf Staging/Live getestet.
|
||
GOOGLE_CLIENT_ID=
|
||
GOOGLE_CLIENT_SECRET=
|
||
GOOGLE_REDIRECT_URI=https://pressekonto.com/auth/google/callback
|
||
|
||
# Empfänger des öffentlichen Kontaktformulars je Brand. Ohne Wert fällt der
|
||
# Versand auf MAIL_FROM_ADDRESS zurück. [EXTERN: echte Adressen eintragen]
|
||
CONTACT_EMAIL_PRESSEKONTO=
|
||
CONTACT_EMAIL_PRESSEECHO=
|
||
CONTACT_EMAIL_BUSINESSPORTAL=
|
||
|
||
# Depublikation (T12): Netto-Preis in Cent fuer die freiwillige Entfernung
|
||
# einer veroeffentlichten PM aus der oeffentlichen Ansicht (Tombstone bleibt).
|
||
# Default 3500 (35 EUR netto, Entscheidung O6 vom 06.07.2026); 0 deaktiviert.
|
||
BILLING_DEPUBLICATION_PRICE_CENTS=3500
|
||
|
||
# Selbst gehostete Umami-Webanalyse (sys.adametz.media; IDs je Brand in
|
||
# config/domains.php). Erst beim Go-Live der Webseiten auf true setzen —
|
||
# die Datenschutzerklärung (Abschnitt 5) beschreibt den Einsatz bereits.
|
||
UMAMI_ENABLED=false
|
||
|
||
# Haupt-E-Mail für alle rechtlichen Anliegen (Impressum, Datenschutz, AGB,
|
||
# Meldeverfahren, Richtlinien) — auf allen drei Domains identisch.
|
||
# Ohne Wert gilt der Default info@pressekonto.com (config/legal.php).
|
||
LEGAL_CONTACT_EMAIL=
|
||
|
||
# Optionales separates DSA-Melde-Postfach je Brand (Impressum + /melden).
|
||
# Ohne Wert fällt die Anzeige auf LEGAL_CONTACT_EMAIL zurück.
|
||
REPORT_EMAIL_PRESSEKONTO=
|
||
REPORT_EMAIL_PRESSEECHO=
|
||
REPORT_EMAIL_BUSINESSPORTAL=
|