Über die API kommt praktisch jede Meldung zweimal, je Portal eine Zeile mit gleichem Text: 9.092 Paare, 53 % des Archivs, bisher jede Fassung self-canonical und in beiden Sitemaps — Google wählte. 1c.7: press_releases.content_hash (SHA-256 über Titel + Text ohne Markup), gesetzt beim Speichern, Bestand per press-releases:hash-content (Query-Builder, Blöcke). Migration in zwei Statements — ADD COLUMN INSTANT, ADD INDEX INPLACE, kein Rebuild. press-releases:duplicates-report zählt Paare je Hauptkategorie. 1c.8: canonical_press_release_id + CanonicalAssigner. Erstquelle folgt der Kategorie (config/canonical.php, Vorschlag zu E9), sonst die zuerst veröffentlichte Fassung. Zuordnung bei jeder Veröffentlichung und über den Bestand (press-releases:assign-canonicals). Die Kopie trägt rel=canonical und mainEntityOfPage auf die Erstquelle; Sitemap und News-Sitemap nennen nur sie; Leser sehen weiterhin beide. Alles hinter CANONICAL_ASSIGNMENT_ENABLED (false), bis E9 entschieden ist. 1c.9: user_canonical in den Index-Prüfungen, sourceVerdict() unterscheidet eigene Adresse / andere eigene Fassung / fremdes Portal; Anzeige im Block „Google-Index" hinter GSC_CUSTOMER_VISIBLE, ohne Aussage über das Warum. Dazu: Abschnitt zur einen Quelle auf /gute-pressemitteilung (Verteiler → viele Kopien, eigene Website zuerst, keine Garantie), noindex in Admin-, Kunden- und Auth-Layouts, UmamiSnippetTest von der lokalen .env gelöst. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
270 lines
9.5 KiB
Text
270 lines
9.5 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}"
|
||
# Bounce-Behandlung (Roadmap 1b.6): Envelope-Sender für Rückläufer — MUSS auf
|
||
# der From-Domain liegen (SPF-Ausrichtung), z. B. bounce@pressekonto.com.
|
||
# Leer = From-Adresse. Schwellen siehe config/bounces.php.
|
||
MAIL_RETURN_PATH=
|
||
# IMAP-Zugang zum Bounce-Postfach für `mail:fetch-bounces` (alle 10 Minuten).
|
||
# Leer = der Befehl tut nichts.
|
||
BOUNCE_IMAP_HOST=
|
||
BOUNCE_IMAP_PORT=993
|
||
BOUNCE_IMAP_ENCRYPTION=ssl
|
||
BOUNCE_IMAP_USERNAME=
|
||
BOUNCE_IMAP_PASSWORD=
|
||
|
||
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=
|
||
|
||
# Crawler-Abruf-Protokoll (Roadmap 1c.0): Tag, ab dem Abrufe durch Such- und
|
||
# KI-Dienste je Meldung erfasst werden — auf den Deploy-Tag setzen.
|
||
CRAWLER_HITS_RECORDED_SINCE=2026-09-18
|
||
|
||
# Google Search Console, URL Inspection API (Roadmap 1c.4). Läuft nur mit
|
||
# Service-Konto (JSON-Schlüssel außerhalb von public/), das in beiden
|
||
# Properties als Nutzer eingetragen ist. Kundenansicht bleibt zu (E8).
|
||
GSC_ENABLED=false
|
||
GSC_SERVICE_ACCOUNT_FILE=
|
||
GSC_PROPERTY_PRESSEECHO=sc-domain:presseecho.de
|
||
GSC_PROPERTY_BUSINESSPORTAL24=sc-domain:businessportal24.de
|
||
GSC_DAILY_BUDGET=300
|
||
GSC_CUSTOMER_VISIBLE=false
|
||
|
||
# Transparenzseite „kostenlos veröffentlichen": Prüfdatum der Portalliste
|
||
# (Einträge in config/transparency.php). Leer = Abschnitt erscheint nicht.
|
||
TRANSPARENCY_FREE_PORTALS_CHECKED_AT=
|
||
|
||
# Doppelveröffentlichung (Roadmap Anhang 2, 1c.8): zweite Fassung verweist per
|
||
# rel=canonical auf die Erstquelle, Zuordnung je Kategorie in config/canonical.php
|
||
# (Entscheidung E9). Aus = jede Fassung bleibt ihr eigenes Canonical.
|
||
CANONICAL_ASSIGNMENT_ENABLED=false
|
||
CANONICAL_FALLBACK=first
|