cms.c2sgmbh/CLAUDE.md
Martin Porwoll 324bd1eb5f docs: add ModSecurity WAF documentation to CLAUDE.md
Document the OWASP CRS 3.3.7 WAF on production nginx, including
exclusion rule IDs and the diagnostic curl method to distinguish
ModSecurity blocks from Payload 403 responses.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 14:02:12 +00:00

446 lines
19 KiB
Markdown

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