presseportale/.env.example
Kevin Adametz 6a34ab48ee technik: Aufrufe über ein Ereignis-Log zählen statt im Request
Bisher lief pro Seitenaufruf ein UPDATE press_releases SET hits = hits + 1 im
Request. Auf der über 1 GB großen Tabelle blockiert dieser Schreibzugriff,
sobald dort eine Schemaänderung läuft — die wartenden Requests belegten dabei
PHP-FPM-Worker und DB-Verbindungen, bis die ganze Seite stand.

Der Request schreibt jetzt nur noch in die kleine Append-Tabelle
press_release_events, und selbst dabei wird jeder Fehler geschluckt und
gemeldet: Ein Aufruf-Zähler darf eine Seite niemals langsamer oder kaputt
machen. Die Summierung in press_releases.hits übernimmt gebündelt der
Scheduler.

Der Fortschritt läuft über eine Wasserstandsmarke (höchste bereits verbuchte
Event-ID) statt über einen Zwischenspeicher, damit bei einem Neustart kein
Aufruf verloren geht. Zähler und Marke werden je Durchgang gemeinsam
committet — sonst wäre nur der Durchgang wiederanlaufsicher, nicht der Abbruch
darin, und der nächste Lauf verbuchte dieselben Aufrufe doppelt. Eine Migration
setzt die Marke beim Deploy auf den Ist-Stand; ohne sie würde der erste Lauf
den gesamten Altbestand ein zweites Mal aufaddieren.

Weil das Log damit mit dem Traffic wächst und als einzige Log-Tabelle des
Systems keine Bereinigung hatte, begrenzt press-releases:purge-events es auf
einen Aufbewahrungszeitraum (Default 395 Tage = 13 Monate, damit der
Vorjahresmonats-Vergleich möglich bleibt). Gelöscht wird ausschließlich
unterhalb der Wasserstandsmarke — alles darüber ist noch nicht in hits verbucht
und wäre sonst dauerhaft verloren. Fehlt die Marke, bricht der Lauf ab, statt
zu raten.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 11:05:28 +00:00

231 lines
7.8 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
# ---------------------------------------------------------------------------
# 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=