17 KiB
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
# 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:
- Code ändern (auf
develop) pnpm build→pm2 restart payload- 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:
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.mjsdirekt (nichtnpx),exec_mode: 'fork' - PgBouncer: Transaction-Mode kann Migrationen stören →
./scripts/db-direct.sh - Tenant SMTP Save (Stand 17.02.2026): Bei conditional
email.smtpkeine Feld-requiredaufhost/user; stattdessen Group-validateverwenden. Tenant-Audit-Hook darf Admin-Save nicht mitawaitblockieren. - 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:
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 & Checklistendocs/DEPLOYMENT_STRATEGY.md- Vollständige Deployment-Strategiedocs/PROJECT_STATUS.md- Projektstatus & Roadmapdocs/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- Sicherheitsrichtliniendocs/anleitungen/FRONTEND.md- Frontend-Entwicklung (sv-frontend)docs/anleitungen/TODO.md- Task-Liste & Changelog
Scripts & Backup
scripts/backup/README.md- Backup-System Dokumentation
Letzte Aktualisierung: 14.02.2026