# Payload CMS Multi-Tenant Project ## Projektübersicht Multi-Tenant CMS für 3 aktive Websites unter einer Payload CMS 3.x Instanz: - porwoll.de - complexcaresolutions.de (c2s) - gunshin.de ## Tech Stack - **CMS:** Payload CMS 3.76.1 - **Framework:** Next.js 16.2.0-canary.41 - **React:** 19.2.3 - **Sprache:** TypeScript - **Runtime:** Node.js 22.x - **Datenbank:** PostgreSQL 17.6 (separater Server) - **Connection Pool:** PgBouncer 1.24.1 (Transaction-Mode) - **Reverse Proxy:** Caddy 2.9.x (Dev) / Nginx (Prod) - **Process Manager:** PM2 - **Package Manager:** pnpm - **Cache:** Redis 7.x (optional, In-Memory-Fallback) - **Job Queue:** BullMQ (Redis-basiert) - **Analytics:** Umami 3.x ## Architektur **Development:** `Internet → Cloudflare → Caddy (sv-caddy) → sv-payload:3000 → PgBouncer:6432 → PostgreSQL (sv-postgres:5432)` | Hostname | IP | Service | |----------|-----|---------| | sv-caddy | 10.10.181.99 | Caddy Reverse Proxy | | sv-payload | 10.10.181.100 | Payload CMS + Redis | | sv-postgres | 10.10.181.101 | PostgreSQL 17 | | sv-frontend | 10.10.181.104 | Multi-Frontend Dev (9 Projekte) | **Production (Hetzner 3):** `cms.c2sgmbh.de:3001` (Payload), `analytics.c2sgmbh.de:3000` (Umami) → Details: `docs/INFRASTRUCTURE.md` ## Multi-Tenant Verwendet `@payloadcms/plugin-multi-tenant`. | ID | Name | Slug | |----|------|------| | 1 | porwoll.de | porwoll | | 4 | Complex Care Solutions GmbH | c2s | | 5 | Gunshin | gunshin | User-Tenant-Zuweisung: Tabelle `users_tenants` ## Wichtige Pfade ``` /home/payload/payload-cms/ ├── src/ │ ├── payload.config.ts # Haupt-Konfiguration │ ├── collections/ # Alle Collections (55+) │ ├── app/(payload)/api/ # Custom API Routes │ ├── lib/ │ │ ├── email/ # E-Mail-System (tenant-spezifisch) │ │ ├── security/ # Rate-Limiter, CSRF, IP-Allowlist │ │ ├── queue/ # BullMQ Jobs & Workers │ │ ├── pdf/ # PDF-Generierung │ │ ├── retention/ # Data Retention (DSGVO) │ │ ├── integrations/meta/ # Facebook + Instagram API │ │ ├── jobs/ # UnifiedSyncService, JobLogger │ │ ├── search.ts # Volltextsuche │ │ └── redis.ts # Redis Cache Client │ └── hooks/ # Collection Hooks ├── tests/ # Unit & Integration Tests ├── scripts/ │ ├── db-direct.sh # Direkte DB-Verbindung (umgeht PgBouncer) │ ├── sync-schema.sh # Schema-Sync nach Collection-Änderungen │ └── backup/ # Backup-System (→ scripts/backup/README.md) ├── .env # Umgebungsvariablen ├── ecosystem.config.cjs # PM2 Config └── .github/workflows/ # CI/CD Pipelines ``` ## Wichtige Befehle ```bash # Entwicklung pnpm dev pnpm build # Migrationen pnpm payload migrate:create pnpm payload migrate # Schema-Sync (wichtig nach Collection-Änderungen!) ./scripts/sync-schema.sh --dry-run # Zeigt Änderungen ./scripts/sync-schema.sh # Führt Sync aus # ImportMap nach Plugin-Änderungen pnpm payload generate:importmap # PM2 pm2 status | pm2 logs payload | pm2 restart payload pm2 logs queue-worker | pm2 restart queue-worker # Tests pnpm test # Alle Tests pnpm test:security # Security Tests pnpm test:access-control # Access Control Tests pnpm test:coverage # Mit Coverage-Report # Lint & Format pnpm lint # ESLint pnpm typecheck # TypeScript Check (4GB heap) pnpm format:check # Prettier Check pnpm format # Prettier Auto-Fix # Direkte DB-Verbindung (umgeht PgBouncer für Migrationen) ./scripts/db-direct.sh migrate ./scripts/db-direct.sh psql # Backup /home/payload/backups/postgres/backup-db.sh --verbose ``` ## Git Workflow **Regel:** Immer auf `develop` entwickeln, nach Freigabe nach `main` mergen. | Branch | Zweck | Deployment | |--------|-------|------------| | `develop` | Aktive Entwicklung | Staging (pl.porwoll.tech) | | `main` | Stabile Version | Production (cms.c2sgmbh.de) | | `feature/*` | Feature-Branches | PR nach develop | **Commit-Konventionen:** `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:` **Workflow nach Code-Änderungen:** 1. Code ändern (auf `develop`) 2. `pnpm build` → `pm2 restart payload` 3. Bei Collection-Änderungen zusätzlich: `pnpm payload migrate:create` ## KRITISCH: Neue Collections hinzufügen Payload CMS verwendet `payload_locked_documents_rels` für Document-Locks. Diese Tabelle benötigt eine `{collection}_id` Spalte für JEDE Collection. Ohne → RSC-Fehler nach Login: ``` column payload_locked_documents_rels.{collection}_id does not exist ``` **Migration MUSS enthalten:** ```sql ALTER TABLE "payload_locked_documents_rels" ADD COLUMN IF NOT EXISTS "{collection}_id" integer REFERENCES {collection}(id) ON DELETE CASCADE; CREATE INDEX IF NOT EXISTS "payload_locked_documents_rels_{collection}_idx" ON "payload_locked_documents_rels" ("{collection}_id"); ``` Beispiel: `src/migrations/20260109_020000_add_blogwoman_collections.ts` **Array-Felder:** Collections mit Arrays benötigen zusätzliche `{collection}_{array_field}` Tabellen. ## Bekannte Besonderheiten & Gotchas - **ES Modules:** `"type": "module"` in package.json → PM2 Config als `.cjs` - **Plugin ImportMap:** Nach Plugin-Änderungen `pnpm payload generate:importmap` - **User-Tenant-Zuweisung:** Neue User müssen manuell Tenants zugewiesen bekommen - **Admin Login:** Custom Route mit Audit-Logging (`src/app/(payload)/api/users/login/route.ts`) - **Queue Worker:** PM2 nutzt `node_modules/tsx/dist/cli.mjs` direkt (nicht `npx`), `exec_mode: 'fork'` - **PgBouncer:** Transaction-Mode kann Migrationen stören → `./scripts/db-direct.sh` - **Tenant SMTP Save (Stand 17.02.2026):** Bei conditional `email.smtp` keine Feld-`required` auf `host/user`; stattdessen Group-`validate` verwenden. Tenant-Audit-Hook darf Admin-Save nicht mit `await` blockieren. - **TRUST_PROXY=true:** PFLICHT hinter Reverse-Proxy, sonst funktionieren Rate-Limiting und IP-Allowlists nicht - **CSRF_SECRET:** PFLICHT in Production (oder PAYLOAD_SECRET) - Server startet nicht ohne ## Build-Konfiguration - `package.json`: `--max-old-space-size=2048` (2GB Heap-Limit) - `next.config.mjs`: `experimental.cpus: 1`, `workerThreads: false` **Server hat 8GB RAM ohne Swap.** Bei laufendem VS Code kann Build OOM werden: ```bash pm2 stop payload NODE_OPTIONS="--no-deprecation --max-old-space-size=1024" ./node_modules/.bin/next build pm2 start payload ``` ## Mehrsprachigkeit Deutsch (default) + Englisch. API: `?locale=de|en`. DB: `_locales` Tabellen. ## URLs | Bereich | URL | |---------|-----| | Admin Panel | https://pl.porwoll.tech/admin | | API | https://pl.porwoll.tech/api | | Swagger UI | https://pl.porwoll.tech/api/docs | | Production | https://cms.c2sgmbh.de | | Monitoring Dashboard | https://pl.porwoll.tech/admin/monitoring | → Vollständige Endpoint-Liste: `docs/anleitungen/API_ANLEITUNG.md` ## Security Kurzübersicht - **Rate-Limiting:** publicApi(60/min), auth(5/15min), email(10/min), search(30/min), form(5/10min) - **CSRF:** Double Submit Cookie + Origin-Validierung, Token: `GET /api/csrf-token` - **IP-Allowlist:** `SEND_EMAIL_ALLOWED_IPS`, `ADMIN_ALLOWED_IPS`, `BLOCKED_IPS` (IPs, CIDRs, Wildcards) - **Data-Masking:** Automatisch in Logs (Passwörter, Tokens, API-Keys) → Details: `docs/anleitungen/SECURITY.md` ## Subsysteme (Kurzübersicht) | System | Schlüsseldateien | Details | |--------|-----------------|---------| | E-Mail (Multi-Tenant SMTP) | `src/lib/email/` | `docs/CLAUDE_REFERENCE.md` | | Newsletter (Double Opt-In) | `src/lib/email/newsletter-service.ts` | `docs/CLAUDE_REFERENCE.md` | | BullMQ Queue | `src/lib/queue/`, `ecosystem.config.cjs` | `docs/CLAUDE_REFERENCE.md` | | Data Retention (DSGVO) | `src/lib/retention/` | `docs/CLAUDE_REFERENCE.md` | | PDF-Generierung | `src/lib/pdf/`, API: `/api/generate-pdf` | `docs/CLAUDE_REFERENCE.md` | | Redis Caching | `src/lib/redis.ts` | `docs/CLAUDE_REFERENCE.md` | | Backup | `scripts/backup/` | `scripts/backup/README.md` | | Community Management | `src/lib/integrations/meta/`, `src/lib/jobs/` | `docs/CLAUDE_REFERENCE.md` | | YouTube Operations Hub | `src/lib/youtube/`, API: `/api/youtube/*` | `docs/CLAUDE_REFERENCE.md` | | FormSubmissions CRM | `src/collections/FormSubmissionsOverrides.ts` | `docs/CLAUDE_REFERENCE.md` | | Monitoring & Alerting | `src/lib/monitoring/`, API: `/api/monitoring/*` | `docs/CLAUDE_REFERENCE.md` | ## Collections (59+) | Slug | Beschreibung | Gruppe | |------|--------------|--------| | users | Benutzer (isSuperAdmin) | Core | | tenants | Mandanten + E-Mail-Config | Core | | media | Medien (11 responsive Sizes) | Core | | pages | Seiten mit Blocks | Core | | posts | Blog/News/Presse | Content | | categories | Post-Kategorien | Content | | tags | Post-Tags | Content | | authors | Post-Autoren | Content | | testimonials | Kundenbewertungen | Content | | faqs | FAQ | Content | | social-links | Social Media Links | Content | | team | Team-Mitglieder | Team | | service-categories | Leistungs-Kategorien | Team | | services | Leistungen | Team | | jobs | Stellenangebote | Team | | portfolios | Portfolio-Galerien | Portfolio | | portfolio-categories | Portfolio-Kategorien | Portfolio | | videos | Video-Bibliothek | Media | | video-categories | Video-Kategorien | Media | | products | Produkte | E-Commerce | | product-categories | Produkt-Kategorien | E-Commerce | | locations | Standorte/Filialen | Feature | | partners | Partner/Kunden | Feature | | downloads | Download-Dateien | Feature | | events | Veranstaltungen | Feature | | timelines | Chronologische Events | Feature | | workflows | Prozesse mit Phasen | Feature | | forms | Formular-Builder (Plugin) | Formulare | | form-submissions | CRM-Workflow | Formulare | | newsletter-subscribers | Double Opt-In | Newsletter | | cookie-configurations | Cookie-Banner | DSGVO | | cookie-inventory | Cookie-Inventar | DSGVO | | consent-logs | Consent-Protokoll (WORM) | DSGVO | | privacy-policy-settings | Datenschutz | DSGVO | | bookings | Fotografie-Buchungen (porwoll) | Tenant | | certifications | Zertifizierungen (c2s) | Tenant | | projects | Game-Dev-Projekte (gunshin) | Tenant | | favorites | Affiliate-Produkte | BlogWoman | | series | YouTube-Serien (Branding) | BlogWoman | | youtube-channels | Multi-Kanal + OAuth | YouTube | | youtube-content | Videos + Shorts | YouTube | | yt-series | Serien + Playlist | YouTube | | yt-notifications | Handlungsbedarf | YouTube | | yt-script-templates | Skript-Vorlagen | YouTube | | yt-batches | Produktions-Batches | YouTube | | yt-checklist-templates | Checklisten | YouTube | | yt-monthly-goals | Monatsziele | YouTube | | yt-tasks | Aufgaben | YouTube | | social-accounts | Meta OAuth | Community | | social-platforms | Plattform-Config | Community | | community-interactions | Kommentare (alle Plattformen) | Community | | community-rules | Auto-Antwort-Regeln | Community | | community-templates | Antwort-Vorlagen | Community | | report-schedules | Geplante Reports | Community | | email-logs | E-Mail-Protokoll | System | | audit-logs | Security Audit Trail | System | | site-settings | Website-Einstellungen (pro Tenant) | System | | navigations | Navigationsmenüs (pro Tenant) | System | | redirects | URL-Weiterleitungen (Plugin) | System | | monitoring-snapshots | Historische System-Metriken | Monitoring | | monitoring-logs | Structured Business-Logs | Monitoring | | monitoring-alert-rules | Konfigurierbare Alert-Regeln | Monitoring | | monitoring-alert-history | Alert-Log (WORM) | Monitoring | ## Blocks (43) | Slug | Beschreibung | Gruppe | |------|--------------|--------| | hero-block | Hero mit Bild, Headline, CTA | Core | | hero-slider-block | Hero-Slider (Slides, Animationen, Autoplay) | Core | | image-slider-block | Bildergalerie/Karussell | Core | | text-block | Rich-Text Inhalt | Core | | image-text-block | Bild + Text nebeneinander | Core | | card-grid-block | Karten-Raster | Core | | quote-block | Zitat | Core | | cta-block | Call-to-Action | Core | | contact-form-block | Kontaktformular | Core | | timeline-block | Timeline-Darstellung | Core | | divider-block | Trennlinie | Core | | video-block | Video einbetten | Core | | posts-list-block | Beitrags-Liste | Content | | testimonials-block | Kundenbewertungen | Content | | newsletter-block | Newsletter-Anmeldung | Content | | process-steps-block | Prozess-Schritte | Content | | faq-block | FAQ-Akkordeon | Content | | team-block | Team-Mitglieder | Content | | services-block | Leistungen | Content | | author-bio-block | Autoren-Biografie | Blogging | | related-posts-block | Verwandte Beiträge | Blogging | | share-buttons-block | Social Share Buttons | Blogging | | table-of-contents-block | Inhaltsverzeichnis | Blogging | | team-filter-block | Team mit Filter | Team | | org-chart-block | Organigramm | Team | | locations-block | Standorte/Filialen | Feature | | logo-grid-block | Partner-Logos | Feature | | stats-block | Statistiken/Zahlen | Feature | | jobs-block | Stellenangebote | Feature | | downloads-block | Download-Bereich | Feature | | map-block | Karten-Einbindung | Feature | | events-block | Veranstaltungen | Interactive | | pricing-block | Preistabellen | Interactive | | tabs-block | Tab-Navigation | Interactive | | accordion-block | Akkordeon | Interactive | | comparison-block | Vergleichstabelle | Interactive | | before-after-block | Vorher/Nachher (porwoll) | Tenant | | favorites-block | Affiliate-Produkte | BlogWoman | | series-block | YouTube-Serien | BlogWoman | | series-detail-block | Serien-Einzelseite | BlogWoman | | video-embed-block | YouTube/Vimeo Embed | BlogWoman | | featured-content-block | Kuratierte Inhalte | BlogWoman | | script-section-block | Script-Abschnitte | BlogWoman | ## Globals | Global | Slug | Beschreibung | |--------|------|--------------| | SEOSettings | seo-settings | Globale SEO-Einstellungen (systemweit) | > SiteSettings, Navigations, PrivacyPolicySettings sind tenant-spezifische Collections. ## Cron Jobs | Endpoint | Schedule | Beschreibung | |----------|----------|--------------| | `/api/cron/community-sync` | alle 15 Min | Kommentar-Sync (YouTube, Facebook, Instagram) | | `/api/cron/token-refresh` | 6:00 + 18:00 UTC | OAuth-Token-Erneuerung | | `/api/cron/send-reports` | stündlich | Community-Reports per E-Mail | | `/api/cron/youtube-sync` | alle 15 Min | YouTube-Kommentar-Sync | | `/api/cron/youtube-channel-sync` | täglich 04:00 UTC | Kanal-Statistiken (Abos, Views) | | `/api/cron/youtube-metrics-sync` | alle 6 Stunden | Video-Performance-Metriken | Auth: `Authorization: Bearer $CRON_SECRET` ## CI/CD | Workflow | Trigger | Beschreibung | |----------|---------|--------------| | `ci.yml` | Push/PR auf main/develop | Lint, Typecheck, Test, Build, E2E | | `security.yml` | Push/PR | Gitleaks, pnpm audit, CodeQL | | `deploy-staging.yml` | Push auf develop | Auto-Deploy auf pl.porwoll.tech | | `deploy-production.yml` | Manual dispatch | Deploy auf cms.c2sgmbh.de (mit Backup + Rollback) | → Details: `docs/DEPLOYMENT.md`, `docs/DEPLOYMENT_STRATEGY.md` ## Umgebungsvariablen Wichtigste Variablen (vollständige Liste in `.env`): | Variable | Beschreibung | Pflicht | |----------|--------------|---------| | `DATABASE_URI` | PostgreSQL via PgBouncer (127.0.0.1:6432) | Ja | | `PAYLOAD_SECRET` | Payload Encryption Secret | Ja | | `TRUST_PROXY` | `true` hinter Reverse-Proxy | Ja | | `CSRF_SECRET` | CSRF-Token Secret (oder PAYLOAD_SECRET) | Prod | | `CRON_SECRET` | Auth für Cron-Endpoints | Ja | | `REDIS_PASSWORD` | Redis Authentifizierung | Ja | | `REDIS_URL` | Redis Cache (localhost:6379) | Optional | | `META_APP_ID/SECRET` | Facebook/Instagram OAuth | Für Community | | `GOOGLE_CLIENT_ID/SECRET` | YouTube OAuth | Für YouTube | Credentials in `~/.pgpass` (chmod 600), nie Klartext in `.env`. ## Dokumentation ### Projekt-Docs - `docs/INFRASTRUCTURE.md` - Infrastruktur (Dev + Prod, PgBouncer, Server) - `docs/DEPLOYMENT.md` - Deployment-Prozess & Checklisten - `docs/DEPLOYMENT_STRATEGY.md` - Vollständige Deployment-Strategie - `docs/PROJECT_STATUS.md` - Projektstatus & Roadmap - `docs/CLAUDE_REFERENCE.md` - **Detaillierte Subsystem-Referenz** (E-Mail, Queue, Retention, Community, etc.) ### Anleitungen - `docs/anleitungen/API_ANLEITUNG.md` - API-Dokumentation (alle Endpoints + curl-Beispiele) - `docs/anleitungen/SECURITY.md` - Sicherheitsrichtlinien - `docs/anleitungen/FRONTEND.md` - Frontend-Entwicklung (sv-frontend) - `docs/anleitungen/TODO.md` - Task-Liste & Changelog ### Scripts & Backup - `scripts/backup/README.md` - Backup-System Dokumentation ## Codex CLI — Remote-Orchestrierung (sv-frontend) OpenAI Codex CLI (`v0.101.0`, Model `gpt-5.3-codex`) ist auf sv-frontend installiert und kann von sv-payload aus non-interaktiv gesteuert werden. **Nutze Codex für Frontend-Aufgaben um Arbeit zu parallelisieren.** **Wichtig:** Mehrere Codex-Sessions können parallel laufen — nutze dies für unabhängige Aufgaben in verschiedenen Projekten. ```bash # Einzelne Aufgabe (JSON-Output für Parsing): ssh sv-frontend "codex exec -s danger-full-access -C ~/frontend.blogwoman.de 'prompt' --json 2>&1" # Aufgabe mit Ergebnis-Datei: ssh sv-frontend "codex exec -s danger-full-access -C ~/projekt -o /tmp/result.txt 'prompt' 2>&1" # Parallele Aufgaben (in separaten SSH-Sessions): ssh sv-frontend "codex exec -s danger-full-access -C ~/frontend.blogwoman.de 'task1' --json" & ssh sv-frontend "codex exec -s danger-full-access -C ~/frontend.porwoll.de 'task2' --json" & wait ``` **Wann Codex delegieren:** - Frontend-Komponenten erstellen/ändern - Lint/Format-Fehler in Frontend-Repos beheben - Code-Reviews in Frontend-Projekten - Tests schreiben für Frontend-Code - Refactoring in Frontend-Repos **Einschränkungen:** - **MUSS** `-s danger-full-access` verwenden (`workspace-write` hat Landlock-Fehler) - `rg` (ripgrep) nicht installiert — Codex nutzt `find` als Fallback - Keine `~/.codex/config.toml` — Konfiguration nur via CLI-Flags - Kein Git-Commit durch Codex — nur Code-Änderungen, Commit/Push über SSH *Letzte Aktualisierung: 22.02.2026*