# Payload CMS - Detaillierte Subsystem-Referenz > Diese Datei enthält detaillierte Dokumentation zu den Subsystemen des Projekts. > Kurzübersicht und Schlüsseldateien: siehe `CLAUDE.md`. ## E-Mail-System Multi-Tenant E-Mail-System mit tenant-spezifischer SMTP-Konfiguration. **Architektur:** - Globaler SMTP als Fallback (via .env) - Tenant-spezifische SMTP in Tenants Collection - Transporter-Caching mit automatischer Invalidierung - EmailLogs Collection für Audit-Trail **Tenant E-Mail-Konfiguration:** ``` Tenants → email → fromAddress, fromName, replyTo → email → useCustomSmtp (Checkbox) → email → smtp → host, port, secure, user, pass ``` **API-Endpoint `/api/send-email`:** ```bash curl -X POST https://pl.porwoll.tech/api/send-email \ -H "Content-Type: application/json" \ -H "Cookie: payload-token=..." \ -d '{ "to": "empfaenger@example.com", "subject": "Betreff", "html": "

Inhalt

", "tenantId": 1 }' ``` **Sicherheit:** - Authentifizierung erforderlich - Tenant-Zugriffskontrolle (User muss Tenant-Mitglied sein) - Rate-Limiting: 10 E-Mails/Minute pro User - SMTP-Passwort nie in API-Responses **Dateien:** - `src/lib/email/tenant-email-service.ts` - Haupt-Service - `src/lib/email/payload-email-adapter.ts` - Payload-Integration - `src/hooks/invalidateEmailCache.ts` - Cache-Invalidierung ### Troubleshooting: Tenant SMTP Save hängt im Admin Stand: 17. Februar 2026 **Symptom:** - Beim Speichern eines Tenants mit aktivem `email.useCustomSmtp` bleibt das Admin Panel bei "wird aktualisiert" hängen. - SMTP-Werte werden nicht zuverlässig in `tenants` persistiert. **Ursachen (behoben):** - Die SMTP-Felder lagen in einer conditional Group (`email.smtp`), während `required: true` auf Unterfeldern (`host`, `user`) serverseitig weiterhin greifen konnte. - Feld-Validierungen nutzten teilweise `siblingData` mit falschem Scope in der verschachtelten Struktur. - `afterChange` im Tenant-Audit-Log wartete synchron auf `payload.create()` für `audit-logs` und konnte die Admin-Response blockieren. **Implementierter Fix:** - Konditionale Pflichtvalidierung auf Gruppenebene `email.smtp.validate` verschoben (Prüfung gegen `email.useCustomSmtp`). - `required: true` von `email.smtp.host` und `email.smtp.user` entfernt; stattdessen explizite Group-Validierung. - Tenant-Audit-Hooks auf Fire-and-Forget umgestellt (`.catch(...)` statt `await`), analog zu User-Audit-Hooks. **Relevante Dateien:** - `src/collections/Tenants.ts` - `src/hooks/auditTenantChanges.ts` ## Newsletter (Double Opt-In) DSGVO-konformes Newsletter-System mit Double Opt-In. **Flow:** 1. User meldet sich an → Status: `pending`, Token wird generiert 2. Double Opt-In E-Mail wird automatisch gesendet 3. User klickt Bestätigungs-Link → Status: `confirmed` 4. Willkommens-E-Mail wird gesendet 5. Abmeldung jederzeit über Link in E-Mails möglich **API-Endpoints:** ```bash # Anmeldung POST /api/newsletter/subscribe {"email": "...", "firstName": "...", "tenantId": 1, "source": "footer"} # Bestätigung (via Link aus E-Mail) GET /api/newsletter/confirm?token= # Abmeldung (via Link aus E-Mail) GET /api/newsletter/unsubscribe?token= ``` **Features:** - Token-Ablauf nach 48 Stunden - Rate-Limiting: 5 Anmeldungen/10 Minuten pro IP - Erneute Anmeldung nach Abmeldung möglich **Dateien:** - `src/lib/email/newsletter-service.ts` - Service-Logik - `src/lib/email/newsletter-templates.ts` - E-Mail-Templates - `src/hooks/sendNewsletterConfirmation.ts` - Hook ## BullMQ Job Queue Asynchrone Job-Verarbeitung mit Redis als Backend. **Queue-Worker:** Läuft als separater PM2-Prozess (`pm2 logs queue-worker`). ### Email Queue ```typescript import { queueEmail } from '@/lib/queue' await queueEmail({ tenantId: 1, to: 'empfaenger@example.com', subject: 'Betreff', html: '

Inhalt

', source: 'form-submission' }) ``` ### PDF Queue ```bash # PDF aus HTML generieren POST /api/generate-pdf {"source": "html", "html": "

Test

", "filename": "test.pdf"} # Job-Status abfragen GET /api/generate-pdf?jobId=abc123 ``` ```typescript import { queuePdfFromHtml, queuePdfFromUrl, getPdfJobStatus } from '@/lib/queue' const job = await queuePdfFromHtml('

Test

', { filename: 'test.pdf' }) const job2 = await queuePdfFromUrl('https://example.com', { format: 'A4' }) const status = await getPdfJobStatus(job.id) ``` ### Worker-Konfiguration Über `ecosystem.config.cjs`: - `QUEUE_EMAIL_CONCURRENCY`: Parallele E-Mail-Jobs (default: 3) - `QUEUE_PDF_CONCURRENCY`: Parallele PDF-Jobs (default: 2) - `QUEUE_RETENTION_CONCURRENCY`: Parallele Retention-Jobs (default: 1) - `QUEUE_DEFAULT_RETRY`: Retry-Versuche (default: 3) - `QUEUE_REDIS_DB`: Redis-Datenbank für Queue (default: 1) - `REDIS_PASSWORD`: Redis-Authentifizierung (Pflicht) **Dateien:** - `src/lib/queue/queue-service.ts` - Zentrale Queue-Verwaltung - `src/lib/queue/jobs/email-job.ts` - E-Mail-Job - `src/lib/queue/jobs/pdf-job.ts` - PDF-Job - `src/lib/queue/workers/email-worker.ts` - E-Mail-Worker - `src/lib/queue/workers/pdf-worker.ts` - PDF-Worker - `scripts/run-queue-worker.ts` - Worker-Starter ## Data Retention Automatische Datenbereinigung für DSGVO-Compliance und Speicheroptimierung. ### Retention Policies | Collection | Retention | Umgebungsvariable | |------------|-----------|-------------------| | email-logs | 90 Tage | `RETENTION_EMAIL_LOGS_DAYS` | | audit-logs | 90 Tage | `RETENTION_AUDIT_LOGS_DAYS` | | consent-logs | 3 Jahre | `RETENTION_CONSENT_LOGS_DAYS` | | media (orphans) | 30 Tage | `RETENTION_MEDIA_ORPHAN_MIN_AGE_DAYS` | Scheduler: Täglich 03:00 Uhr (`RETENTION_CRON_SCHEDULE`). ### API-Endpoint `/api/retention` ```bash # Konfiguration abrufen GET /api/retention # Job-Status GET /api/retention?jobId=abc123 # Manueller Job POST /api/retention {"type": "full"} # Alle Policies {"type": "collection", "collection": "email-logs"} # Einzelne Collection {"type": "media-orphans"} # Nur Media-Orphans ``` **Architektur:** ``` Scheduler (Cron) → Retention Queue (BullMQ) → Retention Worker → Email-Logs (createdAt) + Audit-Logs (createdAt) + Consent-Logs (expiresAt) → Media-Orphan-Cleanup ``` **Dateien:** - `src/lib/retention/retention-config.ts` - Zentrale Konfiguration - `src/lib/retention/cleanup-service.ts` - Lösch-Logik - `src/lib/queue/jobs/retention-job.ts` - Job-Definition - `src/lib/queue/workers/retention-worker.ts` - Worker - `src/app/(payload)/api/retention/route.ts` - API-Endpoint ## Redis Caching Redis erfordert Authentifizierung (`REDIS_PASSWORD`). Eviction-Policy: `noeviction` (BullMQ-Anforderung — verhindert Datenverlust bei Queue-Jobs). ```typescript import { redis } from '@/lib/redis' await redis.set('key', JSON.stringify(data), 'EX', 60) // TTL in Sekunden const cached = await redis.get('key') await redis.keys('posts:*').then(keys => keys.length && redis.del(...keys)) // Pattern-Invalidierung ``` ## FormSubmissions CRM Die FormSubmissions Collection wurde zu einem leichtgewichtigen CRM erweitert. **Status-Workflow:** Neu → Gelesen → In Bearbeitung → Warten → Erledigt → Archiviert **Features:** - Priorität (Hoch/Normal/Niedrig) - Zuständigkeits-Zuweisung an User - Interne Notizen mit Auto-Autor und Zeitstempel - Antwort-Tracking (Methode, Zeitstempel, Zusammenfassung) - Tags zur Kategorisierung - Auto-Markierung als gelesen beim ersten Öffnen **Dateien:** - `src/collections/FormSubmissionsOverrides.ts` - Feld-Definitionen - `src/hooks/formSubmissionHooks.ts` - Automatisierungen ## Community Management System Plattformübergreifendes Community Management für YouTube, Facebook und Instagram. ### Architektur ``` Admin UI (Inbox + Analytics) → /api/community/* (sync, stats, reply, generate-reply, export, stream) → UnifiedSyncService → YouTube Sync + Facebook Sync + Instagram Sync → CommunityInteractions (einheitliche Collection) ``` ### Admin UI **Community Inbox** (`/admin/community/inbox`): - Einheitliche Ansicht aller Kommentare (YouTube + Facebook + Instagram) - Filter nach Plattform, Status, Priorität, Sentiment, Flags - AI-generierte Antwort-Vorschläge (Claude) - Echtzeit-Updates via SSE (`/api/community/stream`) - Export als CSV/JSON **Community Analytics** (`/admin/community/analytics`): - KPI-Cards, Sentiment-Trend, Topic-Cloud, Response-Metriken, Kanal-Vergleich ### Meta (Facebook + Instagram) Integration **OAuth-Flow:** 1. Admin öffnet `/api/auth/meta?socialAccountId=X&accountType=both` 2. Redirect zu Facebook OAuth (12 Scopes) 3. Callback: Short-Lived → Long-Lived Token (60 Tage) 4. Facebook Pages + Instagram Business Accounts werden geladen 5. Token + Account-Daten in SocialAccounts gespeichert **Scopes:** - Facebook: `pages_show_list`, `pages_read_engagement`, `pages_manage_posts`, `pages_read_user_content`, `pages_manage_engagement`, `pages_messaging` - Instagram: `instagram_basic`, `instagram_manage_comments`, `instagram_manage_messages`, `instagram_content_publish` ### API-Endpoints | Endpoint | Methode | Beschreibung | |----------|---------|--------------| | `/api/community/sync` | POST | Manueller Sync (alle Plattformen) | | `/api/community/sync` | GET | Sync-Status | | `/api/community/sync-status` | GET | Detaillierter Status mit Account-Stats | | `/api/community/stats` | GET | Interaktions-Statistiken | | `/api/community/reply` | POST | Antwort senden | | `/api/community/generate-reply` | POST | AI-Antwort generieren (Claude) | | `/api/community/export` | GET | Daten-Export (CSV/JSON) | | `/api/community/stream` | GET | SSE-Stream für Echtzeit-Updates | | `/api/community/analytics/*` | GET | Analytics-Daten (6 Endpoints) | | `/api/auth/meta` | GET | Meta OAuth starten | | `/api/auth/meta/callback` | GET | Meta OAuth Callback | ### Sync-Trigger | Trigger | Endpoint | Plattformen | |---------|----------|-------------| | Cron (alle 15 Min) | `/api/cron/community-sync` | Alle aktiven Accounts | | Manuell (Inbox-Button) | `/api/community/sync` (POST) | Alle (optional filterbar) | | YouTube-spezifisch | `/api/cron/youtube-sync` | Nur YouTube | ### CommunityRules (Automatisierung) **Trigger-Typen:** `keyword`, `sentiment`, `question_detected`, `medical_detected`, `influencer` (>10k Follower), `all_new`, `contains_link`, `contains_email` **Aktionen:** `set_priority`, `assign_to`, `set_flag`, `suggest_template`, `send_notification`, `flag_medical`, `escalate`, `mark_spam`, `set_deadline` ### Wichtige Dateien ``` src/lib/integrations/meta/ ├── MetaBaseClient.ts # HTTP-Basis mit Retry + Pagination ├── FacebookClient.ts # Facebook Graph API Client ├── InstagramClient.ts # Instagram Graph API Client ├── FacebookSyncService.ts # Facebook Kommentar-Sync ├── InstagramSyncService.ts # Instagram Kommentar-Sync ├── oauth.ts # Meta OAuth 2.0 Flow └── index.ts # Re-Exports src/lib/jobs/ ├── UnifiedSyncService.ts # Orchestriert alle Plattform-Syncs ├── syncAllComments.ts # YouTube-spezifischer Sync (Legacy) └── JobLogger.ts # Strukturiertes Logging src/app/(payload)/api/ ├── auth/meta/ # OAuth Start + Callback ├── community/ # Community-Endpoints └── cron/community-sync/ # Cron-Trigger ``` ## Timeline Collection Dedizierte Collection für chronologische Darstellungen. **Typen:** `history`, `milestones`, `releases`, `career`, `events`, `process` **Event-Features:** Flexible Datumsformate, Kategorien, Wichtigkeitsstufen, Bilder, Links, Rich-Text **Display-Optionen:** Layouts (vertikal, alternierend, horizontal, kompakt), Sortierung, Gruppierung nach Jahr **Prozess-spezifische Felder (type=process):** - `stepNumber`, `duration`, `responsible`, `actionRequired`, `deliverables` **API:** `GET /api/timelines?tenant=1[&type=history][&slug=...][&locale=en]` ## Workflows Collection Komplexe Prozess-Darstellungen mit Phasen, Abhängigkeiten und Status-Tracking. **Typen:** `project`, `business`, `approval`, `onboarding`, `support`, `development`, `marketing`, `other` **Phasen-Struktur:** ``` Workflow → Phasen (Array) → name, description, icon, color, estimatedDuration, responsible, deliverables → Schritte (Array) → name, description, stepType, priority, dependencies, conditions, checklist, resources, outputs ``` **Display:** Layouts (vertical, horizontal, flowchart, kanban, gantt), Farbschema (phase, status, priority, brand) **API:** `GET /api/workflows?tenant=1[&type=project][&complexity=medium][&slug=...][&locale=en]` ## HeroSliderBlock Features **Slides (1-10):** Hintergrundbild (Desktop + Mobil), Headline + Subline (lokalisiert), Text-Ausrichtung, Overlay (Farbe, Deckkraft, Gradient), 2 CTA-Buttons **Animationen:** fade, slide, zoom, flip, none (300-1200ms) **Autoplay:** Intervall 3-10s, Pause bei Hover/Interaktion **Navigation:** Pfeile (verschiedene Stile), Dots (Punkte, Striche, Nummern, Thumbnails, Fortschritt), Touch-Swipe, Tastatur **Layout:** Höhe (Vollbild bis kompakt), Mobile-Höhe, Content-Breite ## CI/CD Pipeline Details ### ci.yml (Main CI Pipeline) Läuft bei Push/PR auf `main` und `develop`: - **lint:** ESLint (flat config, 0 errors) - **typecheck:** TypeScript Compiler (`tsc --noEmit`, 4GB heap) - **test:** Unit & Integration Tests (Vitest) - **build:** Next.js Production Build - **e2e:** Playwright E2E Tests ### security.yml Gitleaks (Secret Scanning), pnpm audit, CodeQL (SAST), Security Tests ### deploy-staging.yml - Trigger: Push auf `develop` oder manual dispatch - Ablauf: Pre-checks → SSH → Git Pull → Dependencies → Migrations → Build → PM2 Restart → Health Check - Secret: `STAGING_SSH_KEY` ```bash # Manuelles Staging-Deployment ./scripts/deploy-staging.sh ./scripts/deploy-staging.sh --skip-build ./scripts/deploy-staging.sh --skip-migrations ``` ### deploy-production.yml - Trigger: Manual dispatch - Features: Pre-flight Checks, Database Backup, Health Check, Auto-Rollback - Secrets: `STAGING_SSH_KEY`, `PRODUCTION_SSH_KEY` ```bash gh workflow run deploy-production.yml # Via GitHub Actions ./scripts/deploy-production.sh # Auf Server ./scripts/deploy-production.sh --rollback # Rollback ./scripts/deploy-production.sh --dry-run # Dry-Run ``` ## PgBouncer Connection Pooling Läuft auf dem App-Server (127.0.0.1:6432), pooled Verbindungen zu PostgreSQL (10.10.181.101:5432). **Konfiguration:** `/etc/pgbouncer/pgbouncer.ini` | Parameter | Wert | |-----------|------| | pool_mode | transaction | | default_pool_size | 20 | | min_pool_size | 5 | | reserve_pool_size | 5 | | max_db_connections | 50 | | max_client_conn | 200 | **TLS:** `server_tls_sslmode = require` (TLS 1.3) ```bash # Statistiken PGPASSWORD="$DB_PASSWORD" psql -h 127.0.0.1 -p 6432 -U payload -d pgbouncer -c "SHOW POOLS;" # Direkte Verbindung für Migrationen (umgeht PgBouncer) ./scripts/db-direct.sh migrate ./scripts/db-direct.sh psql ``` ## Cron Jobs (Details) Alle erfordern `Authorization: Bearer $CRON_SECRET`. ```bash # Community Sync manuell curl -X POST "https://your-domain/api/cron/community-sync" \ -H "Authorization: Bearer $CRON_SECRET" \ -d '{"platforms": ["youtube", "facebook"]}' # Token Refresh (Dry-Run) curl "https://your-domain/api/cron/token-refresh?dryRun=true" \ -H "Authorization: Bearer $CRON_SECRET" ``` **Monitoring:** - HEAD-Requests: Status-Header (`X-Sync-Running`, `X-Last-Run`) - Bei laufendem Job: HTTP 423 (Locked) - Fehlerhafte Tokens → `yt-notifications` ## YouTube Operations Hub Umfassendes YouTube-Management-System mit Content-Pipeline, Analytics, Kalender und Task-Management. ### Collections (9) | Slug | Beschreibung | |------|--------------| | `youtube-channels` | Multi-Kanal-Verwaltung mit OAuth | | `youtube-content` | Videos + Shorts mit Status-Pipeline | | `yt-series` | Serien mit Branding (Logo, Farben, Playlist) | | `yt-notifications` | Handlungsbedarf-System | | `yt-tasks` | Aufgaben mit Zuweisung und Fälligkeitsdatum | | `yt-batches` | Produktions-Batches mit Ziel-Tracking | | `yt-checklist-templates` | Checklisten-Vorlagen (Upload, Produktion, Review) | | `yt-monthly-goals` | Monatsziele (Content, Audience, Engagement, Business) | | `yt-script-templates` | Skript-Vorlagen pro Kanal/Serie | ### Content-Pipeline Status `idea` → `script` → `review` → `production` → `editing` → `ready` → `published` Hooks automatisieren Übergänge und erstellen Tasks bei Statuswechsel. ### Custom API Endpoints | Methode | Endpoint | Beschreibung | Auth | |---------|----------|--------------|------| | GET | `/api/youtube/auth` | OAuth-Flow starten (`?socialAccountId=`) | User | | GET | `/api/youtube/callback` | OAuth-Callback, Token-Speicherung | Public (State) | | POST | `/api/youtube/refresh-token` | Access Token erneuern | User + CSRF | | GET | `/api/youtube/dashboard` | Pipeline-Status, Freigaben, Aufgaben | YouTube-Rolle | | GET | `/api/youtube/analytics` | Multi-Tab Analytics (`?tab=&channel=&period=`) | YouTube-Rolle | | GET | `/api/youtube/calendar` | Content-Kalender mit Konflikterkennung | User | | PATCH | `/api/youtube/calendar` | Drag-&-Drop-Umplanung | User | | GET | `/api/youtube/capacity` | Team-Kapazität und Auslastung | User | | GET | `/api/youtube/my-tasks` | Eigene offene Tasks | YouTube-Rolle | | POST | `/api/youtube/complete-task/[id]` | Task als erledigt markieren | Assignee/Manager | | POST/GET | `/api/youtube/upload` | Video-Upload via BullMQ-Queue | User | | POST | `/api/youtube/thumbnails/bulk` | Bulk-Thumbnail-Download (`?dryRun=true`) | SuperAdmin | ### Cron Jobs | Endpoint | Schedule | Beschreibung | |----------|----------|--------------| | `/api/cron/youtube-sync` | alle 15 Min | YouTube-Kommentar-Sync | | `/api/cron/youtube-channel-sync` | täglich 04:00 UTC | Kanal-Statistiken (Abos, Views, Videos) | | `/api/cron/youtube-metrics-sync` | alle 6 Stunden | Video-Performance-Metriken | ### Admin Views - **YouTube Analytics Dashboard** (`/admin/views/youtube-analytics`) — 7 Tabs: Performance, Pipeline, Goals, Community, Comparison, Trends, ROI - **Content Calendar** (`/admin/views/content-calendar`) — FullCalendar mit Drag-&-Drop und Konflikterkennung ### Services & Utilities | Datei | Beschreibung | |-------|--------------| | `src/lib/youtube/ConflictDetectionService.ts` | Erkennt Terminüberschneidungen bei Content-Planung | | `src/lib/youtube/analytics-helpers.ts` | Trend-Berechnung, Vergleiche, ROI | | `src/lib/youtube/capacity-calculator.ts` | Team-Auslastungsberechnung | | `src/lib/youtube/youtubeAccess.ts` | Rollen-basierte Zugriffskontrolle | ### Hooks (Automatisierung) - **youtubeChannels/downloadChannelImage** — Profilbild automatisch herunterladen - **youtubeContent/downloadThumbnail** — Video-Thumbnails automatisch herunterladen - **youtubeContent/createTasksOnStatusChange** — Tasks bei Statuswechsel erstellen - **youtubeContent/autoStatusTransitions** — Automatische Pipeline-Übergänge - **ytTasks/notifyOnAssignment** — Benachrichtigung bei Task-Zuweisung ### Zugriffskontrolle | Funktion | Beschreibung | |----------|--------------| | `hasYouTubeAccess()` | Lese-Zugriff (YouTube-Rolle) | | `isYouTubeManager()` | Manager-Level (Erstellen, Löschen, Freigaben) | | `isYouTubeCreatorOrAbove()` | Creator+ Zugriff | | `canAccessAssignedContent()` | Nur zugewiesene Inhalte | | `canAccessOwnNotifications()` | Nur eigene Benachrichtigungen |