cms.c2sgmbh/CLAUDE.md
Martin Porwoll 8868a5be30 feat: add Services collection and block
- Add ServiceCategories collection for grouping services
- Add Services collection with comprehensive service profiles:
  - Title, slug, descriptions (short + full)
  - Icon (text or image) and image gallery
  - Category relationship for grouping
  - Features/benefits array
  - Flexible pricing (on-request default, fixed, hourly, range, etc.)
  - CTA buttons (primary + secondary)
  - Related services, team members, and FAQs relationships
  - Detail page sections with testimonials
  - SEO fields (meta title, description, OG image)
  - Status flags (active, featured, new badge)
- Add ServicesBlock with 8 layouts:
  - Grid, List, Tabs, Accordion, Featured+Grid, Slider, Compact, Masonry
- Multi-tenant enabled via plugin configuration
- Update documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-10 07:39:03 +00:00

13 KiB

Payload CMS Multi-Tenant Project

Projektübersicht

Multi-Tenant CMS für 4 Websites unter einer Payload CMS 3.x Instanz:

  • porwoll.de
  • complexcaresolutions.de
  • gunshin.de
  • zweitmein.ng

Tech Stack

  • CMS: Payload CMS 3.x
  • Framework: Next.js 15.4.7
  • Sprache: TypeScript
  • Datenbank: PostgreSQL 17 (separater Server)
  • Reverse Proxy: Caddy 2.10.2 mit Let's Encrypt
  • Process Manager: PM2
  • Package Manager: pnpm
  • Cache: Redis (optional, mit In-Memory-Fallback)
  • Job Queue: BullMQ (Redis-basiert)

Architektur

Internet → 37.24.237.181 → Caddy (443) → Payload (3000)
                                              ↓
                              PostgreSQL (10.10.181.101:5432)
Server IP Funktion
sv-payload (LXC 700) 10.10.181.100 App Server
sv-postgres (LXC 701) 10.10.181.101 Datenbank

Wichtige Pfade

/home/payload/payload-cms/     # Projektroot
├── src/
│   ├── payload.config.ts      # Haupt-Konfiguration
│   ├── collections/           # Alle Collections
│   │   ├── Users.ts
│   │   ├── Media.ts
│   │   ├── Tenants.ts
│   │   ├── Posts.ts
│   │   ├── Categories.ts
│   │   ├── Portfolios.ts
│   │   ├── PortfolioCategories.ts
│   │   ├── FAQs.ts
│   │   ├── Team.ts
│   │   ├── ServiceCategories.ts
│   │   ├── Services.ts
│   │   ├── EmailLogs.ts
│   │   ├── AuditLogs.ts
│   │   └── ...
│   ├── app/(payload)/api/     # Custom API Routes
│   │   ├── users/login/route.ts    # Custom Login mit Audit
│   │   ├── send-email/route.ts
│   │   ├── email-logs/
│   │   │   ├── export/route.ts
│   │   │   └── stats/route.ts
│   │   └── test-email/route.ts
│   ├── lib/
│   │   ├── email/             # E-Mail-System
│   │   │   ├── tenant-email-service.ts
│   │   │   └── payload-email-adapter.ts
│   │   ├── security/          # Security-Module
│   │   │   ├── rate-limiter.ts
│   │   │   ├── csrf.ts
│   │   │   ├── ip-allowlist.ts
│   │   │   └── data-masking.ts
│   │   ├── queue/             # BullMQ Job Queue
│   │   │   ├── queue-service.ts
│   │   │   ├── jobs/email-job.ts
│   │   │   ├── jobs/pdf-job.ts
│   │   │   ├── workers/email-worker.ts
│   │   │   └── workers/pdf-worker.ts
│   │   ├── pdf/               # PDF-Generierung
│   │   │   └── pdf-service.ts
│   │   ├── search.ts          # Volltextsuche
│   │   └── redis.ts           # Redis Cache Client
│   └── hooks/                 # Collection Hooks
│       ├── sendFormNotification.ts
│       ├── invalidateEmailCache.ts
│       └── auditLog.ts
├── tests/                     # Test Suite
│   ├── unit/security/         # Security Unit Tests
│   └── int/                   # Integration Tests
├── scripts/
│   └── run-queue-worker.ts    # Queue Worker Starter
├── .env                       # Umgebungsvariablen
├── ecosystem.config.cjs       # PM2 Config
└── .next/                     # Build Output

Umgebungsvariablen (.env)

DATABASE_URI=postgresql://payload:Finden55@10.10.181.101:5432/payload_db
PAYLOAD_SECRET=a53b254070d3fffd2b5cfcc3
PAYLOAD_PUBLIC_SERVER_URL=https://pl.c2sgmbh.de
NEXT_PUBLIC_SERVER_URL=https://pl.c2sgmbh.de
NODE_ENV=production
PORT=3000

# E-Mail (Global Fallback)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=user@example.com
SMTP_PASS=secret
SMTP_FROM_ADDRESS=noreply@c2sgmbh.de
SMTP_FROM_NAME=Payload CMS

# Redis Cache
REDIS_URL=redis://localhost:6379

# Security
CSRF_SECRET=your-csrf-secret
SEND_EMAIL_ALLOWED_IPS=  # Optional: Komma-separierte IPs/CIDRs
BLOCKED_IPS=             # Optional: Global geblockte IPs

Multi-Tenant Plugin

Verwendet @payloadcms/plugin-multi-tenant für Mandantenfähigkeit.

Aktuelle Tenants:

ID Name Slug
1 porwoll.de porwoll
4 Complex Care Solutions GmbH c2s
5 Gunshin gunshin

User-Tenant-Zuweisung: Tabelle users_tenants

Wichtige Befehle

# Entwicklung
pnpm dev

# Production Build
pnpm build

# Migrationen
pnpm payload migrate:create
pnpm payload migrate

# ImportMap nach Plugin-Änderungen
pnpm payload generate:importmap

# PM2
pm2 status
pm2 logs payload
pm2 logs queue-worker
pm2 restart payload
pm2 restart queue-worker

# Tests
pnpm test              # Alle Tests
pnpm test:security     # Security Tests
pnpm test:coverage     # Mit Coverage-Report

# Datenbank prüfen
PGPASSWORD=Finden55 psql -h 10.10.181.101 -U payload -d payload_db

Workflow nach Code-Änderungen

  1. Code ändern
  2. pnpm build
  3. pm2 restart payload
  4. Testen unter https://pl.c2sgmbh.de/admin

Bekannte Besonderheiten

  • ES Modules: package.json hat "type": "module", daher PM2 Config als .cjs
  • Plugin ImportMap: Nach Plugin-Änderungen pnpm payload generate:importmap ausführen
  • User-Tenant-Zuweisung: Neue User müssen manuell Tenants zugewiesen bekommen
  • Admin Login: Custom Route mit Audit-Logging, unterstützt JSON und _payload FormData

Build-Konfiguration

Der Build ist für speichereffizientes Kompilieren optimiert:

  • package.json: --max-old-space-size=2048 (2GB Heap-Limit)
  • next.config.mjs: experimental.cpus: 1, workerThreads: false

WICHTIG: Der Server hat 8GB RAM ohne Swap. Bei laufendem VS Code Server kann der Build mit reduziertem Memory ausgeführt werden:

pm2 stop payload  # Speicher freigeben
NODE_OPTIONS="--no-deprecation --max-old-space-size=1024" ./node_modules/.bin/next build
pm2 start payload

Ohne PM2-Stop und mit VS Code wird der Build vom OOM Killer beendet (Exit code 137).

Mehrsprachigkeit (i18n)

Das System unterstützt Deutsch (default) und Englisch:

  • Admin UI: @payloadcms/translations für DE/EN
  • Content: Localization mit Fallback auf Deutsch
  • Datenbank: 36 _locales Tabellen für lokalisierte Felder
  • API: ?locale=de oder ?locale=en Parameter
  • Frontend: Routing über /[locale]/...
# Locales in der Datenbank prüfen
PGPASSWORD=Finden55 psql -h 10.10.181.101 -U payload -d payload_db -c "\dt *_locales"

URLs

Security-Features

Rate-Limiting

Zentraler Rate-Limiter mit vordefinierten Limits:

  • publicApi: 60 Requests/Minute
  • auth: 5 Requests/15 Minuten (Login)
  • email: 10 Requests/Minute
  • search: 30 Requests/Minute
  • form: 5 Requests/10 Minuten

CSRF-Schutz

  • Double Submit Cookie Pattern
  • Origin-Header-Validierung
  • Token-Endpoint: GET /api/csrf-token
  • Admin-Panel hat eigenen CSRF-Schutz

IP-Allowlist

  • Konfigurierbar via SEND_EMAIL_ALLOWED_IPS
  • Unterstützt IPs, CIDRs (192.168.1.0/24) und Wildcards (10.10.*.*)
  • Globale Blocklist via BLOCKED_IPS

Data-Masking

  • Automatische Maskierung sensibler Daten in Logs
  • Erkennt Passwörter, Tokens, API-Keys, SMTP-Credentials
  • Safe-Logger-Factory für konsistentes Logging

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:

curl -X POST https://pl.c2sgmbh.de/api/send-email \
  -H "Content-Type: application/json" \
  -H "Cookie: payload-token=..." \
  -d '{
    "to": "empfaenger@example.com",
    "subject": "Betreff",
    "html": "<p>Inhalt</p>",
    "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

BullMQ Job Queue

Das System verwendet BullMQ für asynchrone Job-Verarbeitung mit Redis als Backend.

PM2 Worker

Der Queue-Worker läuft als separater PM2-Prozess:

# Worker-Status prüfen
pm2 status
pm2 logs queue-worker

# Worker neustarten
pm2 restart queue-worker

Email Queue

E-Mails können asynchron über die Queue versendet werden:

import { queueEmail } from '@/lib/queue'

await queueEmail({
  tenantId: 1,
  to: 'empfaenger@example.com',
  subject: 'Betreff',
  html: '<p>Inhalt</p>',
  source: 'form-submission'
})

PDF Queue

PDF-Generierung erfolgt über Playwright (HTML/URL zu PDF):

API-Endpoint /api/generate-pdf:

# PDF aus HTML generieren
curl -X POST https://pl.c2sgmbh.de/api/generate-pdf \
  -H "Content-Type: application/json" \
  -H "Cookie: payload-token=..." \
  -d '{
    "source": "html",
    "html": "<h1>Test</h1><p>Inhalt</p>",
    "filename": "test.pdf"
  }'

# Job-Status abfragen
curl "https://pl.c2sgmbh.de/api/generate-pdf?jobId=abc123" \
  -H "Cookie: payload-token=..."

Programmatisch:

import { queuePdfFromHtml, queuePdfFromUrl, getPdfJobStatus } from '@/lib/queue'

// HTML zu PDF
const job = await queuePdfFromHtml('<h1>Test</h1>', { filename: 'test.pdf' })

// URL zu PDF
const job2 = await queuePdfFromUrl('https://example.com', { format: 'A4' })

// Status abfragen
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_DEFAULT_RETRY: Retry-Versuche (default: 3)
  • QUEUE_REDIS_DB: Redis-Datenbank für Queue (default: 1)

Redis Caching

Redis wird für API-Response-Caching und E-Mail-Transporter-Caching verwendet:

import { redis } from '@/lib/redis'

// Cache setzen (TTL in Sekunden)
await redis.set('key', JSON.stringify(data), 'EX', 60)

// Cache lesen
const cached = await redis.get('key')

// Pattern-basierte Invalidierung
await redis.keys('posts:*').then(keys => keys.length && redis.del(...keys))

Datenbank-Direktzugriff

PGPASSWORD=Finden55 psql -h 10.10.181.101 -U payload -d payload_db

# Nützliche Queries
SELECT * FROM tenants;
SELECT * FROM users_tenants;
SELECT * FROM email_logs ORDER BY created_at DESC LIMIT 10;
SELECT * FROM audit_logs ORDER BY created_at DESC LIMIT 10;
\dt  -- Alle Tabellen

Collections Übersicht

Collection Slug Beschreibung
Users users Benutzer mit isSuperAdmin Flag
Tenants tenants Mandanten mit E-Mail-Konfiguration
Media media Medien mit 11 responsive Image Sizes
Pages pages Seiten mit Blocks
Posts posts Blog/News/Presse mit Kategorien
Categories categories Kategorien für Posts
Portfolios portfolios Portfolio-Galerien (Fotografie)
PortfolioCategories portfolio-categories Kategorien für Portfolios
Testimonials testimonials Kundenbewertungen
FAQs faqs Häufig gestellte Fragen (FAQ)
Team team Team-Mitglieder und Mitarbeiter
ServiceCategories service-categories Kategorien für Leistungen
Services services Leistungen und Dienstleistungen
NewsletterSubscribers newsletter-subscribers Newsletter mit Double Opt-In
SocialLinks social-links Social Media Links
Forms forms Formular-Builder
FormSubmissions form-submissions Formular-Einsendungen
EmailLogs email-logs E-Mail-Protokollierung
AuditLogs audit-logs Security Audit Trail
CookieConfigurations cookie-configurations Cookie-Banner Konfiguration
CookieInventory cookie-inventory Cookie-Inventar
ConsentLogs consent-logs Consent-Protokollierung

Globals

Global Slug Beschreibung
SiteSettings site-settings Allgemeine Website-Einstellungen
Navigation navigation Navigationsmenü
SEOSettings seo-settings SEO-Einstellungen
PrivacyPolicySettings privacy-policy-settings Datenschutz-Einstellungen

Test Suite

# Alle Tests ausführen
pnpm test

# Security Tests
pnpm test:security

# Coverage Report
pnpm test:coverage

Test Coverage Thresholds:

  • Lines: 35%
  • Functions: 50%
  • Branches: 65%

Dokumentation

  • CLAUDE.md - Diese Datei (Projekt-Übersicht)
  • docs/INFRASTRUCTURE.md - Server-Architektur & Deployment
  • docs/anleitungen/TODO.md - Task-Liste & Roadmap
  • docs/anleitungen/SECURITY.md - Sicherheitsrichtlinien

Letzte Aktualisierung: 10.12.2025