# 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.77.0
- **Framework:** Next.js 16.2.0-canary.58
- **React:** 19.2.4
- **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):** `Internet → Nginx (ModSecurity WAF) → localhost: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
- **ModSecurity WAF (Production):** Nginx auf Hetzner 3 hat OWASP CRS 3.3.7 in Blocking-Mode. `/admin` und `/api/` sind per Exclusion-Rules freigeschaltet (IDs 1000160/1000170). Bei neuen API-Pfaden außerhalb von `/api/` muss ggf. eine neue Exclusion-Rule in `/etc/modsecurity/crs/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf` ergänzt werden. **Diagnose:** `curl localhost:3001/...` (JSON=Payload) vs `curl https://cms.c2sgmbh.de/...` (HTML=ModSecurity-Block)

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