# 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.69.0
- **Framework:** Next.js 15.5.9
- **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 mit Let's Encrypt (Dev) / Nginx (Prod)
- **Process Manager:** PM2
- **Package Manager:** pnpm
- **Cache:** Redis 7.x (optional, mit In-Memory-Fallback)
- **Job Queue:** BullMQ (Redis-basiert)
- **Analytics:** Umami 3.x
- **AI Tools:** Claude Code, Codex CLI, Gemini CLI

## Architektur

### Development (VLAN 181 - porwoll.tech)

```
Internet → Cloudflare → 37.24.237.181 → Caddy (sv-caddy) → Services
                                                               ↓
                                    ┌───────────────────────────┴───────────────────────────┐
                                    │                                                       │
                              sv-payload:3000                                        sv-frontend:3000-3008
                              (Payload CMS)                                          (9 Frontend-Projekte)
                                    │
                              PgBouncer (6432)
                                    │
                              PostgreSQL (sv-postgres:5432)
```

| LXC | Hostname | IP | Service | Status |
|-----|----------|-----|---------|--------|
| 699 | sv-caddy | 10.10.181.99 | Caddy Reverse Proxy | ✅ Running |
| 700 | sv-payload | 10.10.181.100 | Payload CMS + Redis | ✅ Running |
| 701 | sv-postgres | 10.10.181.101 | PostgreSQL 17 + Redis Commander | ✅ Running |
| 702 | sv-dev-payload | 10.10.181.102 | Payload Experimental | ⏸️ Stopped |
| 703 | sv-analytics | 10.10.181.103 | Umami Analytics | ✅ Running |
| 704 | sv-frontend | 10.10.181.104 | Multi-Frontend Dev (9 Projekte) | ✅ Running |

### Production (Hetzner 3)

| Service | URL | Port |
|---------|-----|------|
| Payload CMS | https://cms.c2sgmbh.de | 3001 |
| Umami Analytics | https://analytics.c2sgmbh.de | 3000 |
| PostgreSQL 17 | localhost | 5432 |
| Redis Cache | localhost | 6379 |

## 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
│   │   │   ├── newsletter-service.ts    # Newsletter Double Opt-In
│   │   │   └── newsletter-templates.ts  # E-Mail-Templates
│   │   ├── 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
│       ├── sendNewsletterConfirmation.ts  # Newsletter Double Opt-In
│       ├── invalidateEmailCache.ts
│       └── auditLog.ts
├── tests/                     # Test Suite
│   ├── unit/security/         # Security Unit Tests
│   └── int/                   # Integration Tests
├── scripts/
│   ├── run-queue-worker.ts    # Queue Worker Starter
│   └── backup/                # Backup-System
│       ├── backup-db.sh       # PostgreSQL Backup-Skript
│       ├── restore-db.sh      # PostgreSQL Restore-Skript
│       ├── setup-backup.sh    # Interaktives Setup
│       └── README.md          # Backup-Dokumentation
├── .env                       # Umgebungsvariablen
├── ecosystem.config.cjs       # PM2 Config
└── .next/                     # Build Output
```

## Umgebungsvariablen (.env)

> **Hinweis:** Sensible Credentials (DB_PASSWORD, SMTP_PASS, etc.) werden aus `~/.pgpass`
> und Umgebungsvariablen gelesen. Niemals Klartext-Passwörter in diese Datei schreiben!

```env
# Datenbank (Passwort in ~/.pgpass oder als Umgebungsvariable)
DATABASE_URI=postgresql://payload:${DB_PASSWORD}@127.0.0.1:6432/payload_db
PAYLOAD_SECRET=a53b254070d3fffd2b5cfcc3
PAYLOAD_PUBLIC_SERVER_URL=https://pl.porwoll.tech
NEXT_PUBLIC_SERVER_URL=https://pl.porwoll.tech
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              # PFLICHT in Production (oder PAYLOAD_SECRET)
TRUST_PROXY=true                          # PFLICHT hinter Reverse-Proxy (Caddy/Nginx)
SEND_EMAIL_ALLOWED_IPS=                   # Optional: Komma-separierte IPs/CIDRs
ADMIN_ALLOWED_IPS=                        # Optional: IP-Beschränkung für Admin-Panel
BLOCKED_IPS=                              # Optional: Global geblockte IPs
```

> **Wichtig:** `TRUST_PROXY=true` muss gesetzt sein wenn die App hinter einem Reverse-Proxy
> (wie Caddy) läuft. Ohne diese Einstellung funktionieren IP-basierte Sicherheitsfunktionen
> (Rate-Limiting, IP-Allowlists, Blocklists) nicht korrekt.

## PgBouncer Connection Pooling

PgBouncer läuft auf dem App-Server und pooled Datenbankverbindungen:

```bash
# Konfiguration
/etc/pgbouncer/pgbouncer.ini
/etc/pgbouncer/userlist.txt   # chmod 600

# Service
sudo systemctl status pgbouncer
sudo systemctl restart pgbouncer

# Statistiken abfragen
# Pool-Statistiken (Passwort aus ~/.pgpass)
PGPASSWORD="$DB_PASSWORD" psql -h 127.0.0.1 -p 6432 -U payload -d pgbouncer -c "SHOW POOLS;"
PGPASSWORD="$DB_PASSWORD" psql -h 127.0.0.1 -p 6432 -U payload -d pgbouncer -c "SHOW STATS;"
```

**Konfiguration:**
| Parameter | Wert | Beschreibung |
|-----------|------|--------------|
| pool_mode | transaction | Verbindung wird nach Transaktion freigegeben |
| default_pool_size | 20 | Standard-Pool-Größe pro DB/User |
| min_pool_size | 5 | Mindestens gehaltene Verbindungen |
| reserve_pool_size | 5 | Reserve für Lastspitzen |
| max_db_connections | 50 | Max. Verbindungen zu PostgreSQL |
| max_client_conn | 200 | Max. Clients zu PgBouncer |

**Verbindungen:**
- App → PgBouncer: `127.0.0.1:6432` (DATABASE_URI)
- PgBouncer → PostgreSQL: `10.10.181.101:5432` (TLS 1.3, `server_tls_sslmode = require`)

**Direkte Verbindung (für Migrationen/CLI):**

PgBouncer im Transaction-Mode kann Probleme mit lang laufenden Migrationen verursachen.
Für solche Fälle das `db-direct.sh` Skript verwenden:

```bash
# Migrationen direkt an PostgreSQL (umgeht PgBouncer)
./scripts/db-direct.sh migrate

# Interaktive psql-Session
./scripts/db-direct.sh psql

# Schema-Änderungen
./scripts/db-direct.sh migrate:create
```

Das Skript liest Credentials aus `~/.pgpass` (chmod 600).

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

```bash
# Entwicklung
pnpm dev

# Production Build
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 logs queue-worker
pm2 restart payload
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

# Datenbank prüfen
# Verwende ./scripts/db-direct.sh psql oder:
PGPASSWORD="$DB_PASSWORD" psql -h 10.10.181.101 -U payload -d payload_db

# Backup
/home/payload/backups/postgres/backup-db.sh --verbose  # Manuelles Backup
scripts/backup/setup-backup.sh                          # Backup-System einrichten
```

## Workflow nach Code-Änderungen

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

### Bei Collection/Global-Änderungen (zusätzlich)

1. `pnpm payload migrate:create` - Migration erstellen
2. `git add src/migrations/` - Migration committen
3. Auf PROD nach Deployment: `./scripts/sync-schema.sh` wird automatisch ausgeführt

### ⚠️ KRITISCH: Neue Collections hinzufügen

Beim Hinzufügen einer neuen Collection müssen **System-Tabellen** manuell aktualisiert werden!

**Problem:** Payload CMS verwendet `payload_locked_documents_rels` um Document-Locks über alle Collections zu tracken. Diese Tabelle benötigt eine `{collection}_id` Spalte für JEDE Collection. Ohne diese Spalte tritt nach dem Login ein RSC-Fehler auf:
```
Error: An error occurred in the Server Components render
caused by: column payload_locked_documents_rels.{collection}_id does not exist
```

**Lösung:** Die Migration für eine neue Collection MUSS folgendes enthalten:

```sql
-- 1. Collection-Tabellen erstellen (normal)
CREATE TABLE IF NOT EXISTS "{collection}" (...);
CREATE TABLE IF NOT EXISTS "{collection}_rels" (...);  -- falls benötigt
CREATE TABLE IF NOT EXISTS "{collection}_locales" (...);  -- falls lokalisiert

-- 2. KRITISCH: System-Tabelle aktualisieren!
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-Migration:** `src/migrations/20260109_020000_add_blogwoman_collections.ts`

**Array-Felder:** Collections mit Array-Feldern (z.B. `hours_structured` in Locations) benötigen zusätzliche Tabellen:
```sql
CREATE TABLE IF NOT EXISTS "{collection}_{array_field}" (
  "id" serial PRIMARY KEY NOT NULL,
  "_order" integer NOT NULL,
  "_parent_id" integer NOT NULL REFERENCES {collection}(id) ON DELETE CASCADE,
  -- Array-Feld-Spalten hier
);
```

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

```bash
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]/...`

```bash
# Locales in der Datenbank prüfen
# Verwende ./scripts/db-direct.sh psql oder:
PGPASSWORD="$DB_PASSWORD" psql -h 10.10.181.101 -U payload -d payload_db -c "\dt *_locales"
```

## URLs

- **Admin Panel:** https://pl.porwoll.tech/admin
- **API:** https://pl.porwoll.tech/api
- **API-Dokumentation (Swagger UI):** https://pl.porwoll.tech/api/docs
- **OpenAPI JSON:** https://pl.porwoll.tech/api/openapi.json
- **E-Mail API:** https://pl.porwoll.tech/api/send-email (POST, Auth erforderlich)
- **Test-E-Mail:** https://pl.porwoll.tech/api/test-email (POST, Admin erforderlich)
- **E-Mail Stats:** https://pl.porwoll.tech/api/email-logs/stats (GET, Auth erforderlich)
- **PDF-Generierung:** https://pl.porwoll.tech/api/generate-pdf (POST/GET, Auth erforderlich)
- **Newsletter Anmeldung:** https://pl.porwoll.tech/api/newsletter/subscribe (POST, öffentlich)
- **Newsletter Bestätigung:** https://pl.porwoll.tech/api/newsletter/confirm (GET/POST)
- **Newsletter Abmeldung:** https://pl.porwoll.tech/api/newsletter/unsubscribe (GET/POST)
- **Timeline API:** https://pl.porwoll.tech/api/timelines (GET, öffentlich, tenant required)
- **Workflows API:** https://pl.porwoll.tech/api/workflows (GET, öffentlich, tenant required)
- **Data Retention API:** https://pl.porwoll.tech/api/retention (GET/POST, Super-Admin erforderlich)

## Security-Features

### Proxy-Vertrauen (TRUST_PROXY)
**WICHTIG:** Wenn die App hinter einem Reverse-Proxy läuft (Caddy, Nginx, etc.),
muss `TRUST_PROXY=true` gesetzt sein:

```env
TRUST_PROXY=true
```

Ohne diese Einstellung:
- Werden `X-Forwarded-For` und `X-Real-IP` Header ignoriert
- Können Angreifer IP-basierte Sicherheitsmaßnahmen nicht umgehen
- Aber: Rate-Limiting und IP-Allowlists funktionieren nicht korrekt

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

**Hinweis:** Rate-Limiting basiert auf Client-IP. Bei `TRUST_PROXY=false` wird
`direct-connection` als IP verwendet, was alle Clients zusammenfasst.

### CSRF-Schutz
- Double Submit Cookie Pattern
- Origin-Header-Validierung
- Token-Endpoint: `GET /api/csrf-token`
- Admin-Panel hat eigenen CSRF-Schutz
- **CSRF_SECRET oder PAYLOAD_SECRET ist in Production PFLICHT**
- Server startet nicht ohne Secret in Production

### IP-Allowlist
- Konfigurierbar via `SEND_EMAIL_ALLOWED_IPS`, `ADMIN_ALLOWED_IPS`
- Unterstützt IPs, CIDRs (`192.168.1.0/24`) und Wildcards (`10.10.*.*`)
- Globale Blocklist via `BLOCKED_IPS`
- **Warnung:** Leere Allowlists erlauben standardmäßig alle 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`:**
```bash
curl -X POST https://pl.porwoll.tech/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

### Newsletter Double Opt-In

DSGVO-konformes Newsletter-System mit Double Opt-In:

**Flow:**
1. User meldet sich an → Status: `pending`, Token wird generiert
2. Double Opt-In E-Mail wird automatisch gesendet
3. User klickt Bestätigungs-Link → Status: `confirmed`
4. Willkommens-E-Mail wird gesendet
5. Abmeldung jederzeit über Link in E-Mails möglich

**API-Endpoints:**

```bash
# Newsletter-Anmeldung
curl -X POST https://pl.porwoll.tech/api/newsletter/subscribe \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "firstName": "Max",
    "tenantId": 1,
    "source": "footer"
  }'

# Bestätigung (via Link aus E-Mail)
GET https://pl.porwoll.tech/api/newsletter/confirm?token=<uuid>

# Abmeldung (via Link aus E-Mail)
GET https://pl.porwoll.tech/api/newsletter/unsubscribe?token=<uuid>
```

**Features:**
- Automatischer E-Mail-Versand bei Anmeldung
- Token-Ablauf nach 48 Stunden
- Willkommens-E-Mail nach Bestätigung
- Abmelde-Bestätigung per E-Mail
- Rate-Limiting: 5 Anmeldungen/10 Minuten pro IP
- Erneute Anmeldung nach Abmeldung möglich

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

```bash
# 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:

```typescript
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`:**
```bash
# PDF aus HTML generieren
curl -X POST https://pl.porwoll.tech/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.porwoll.tech/api/generate-pdf?jobId=abc123" \
  -H "Cookie: payload-token=..."
```

**Programmatisch:**
```typescript
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_RETENTION_CONCURRENCY`: Parallele Retention-Jobs (default: 1)
- `QUEUE_DEFAULT_RETRY`: Retry-Versuche (default: 3)
- `QUEUE_REDIS_DB`: Redis-Datenbank für Queue (default: 1)

## Data Retention

Automatische Datenbereinigung für DSGVO-Compliance und Speicheroptimierung.

### Retention Policies

| Collection | Retention | Umgebungsvariable | Beschreibung |
|------------|-----------|-------------------|--------------|
| email-logs | 90 Tage | `RETENTION_EMAIL_LOGS_DAYS` | E-Mail-Protokolle |
| audit-logs | 90 Tage | `RETENTION_AUDIT_LOGS_DAYS` | Audit-Trail |
| consent-logs | 3 Jahre | `RETENTION_CONSENT_LOGS_DAYS` | DSGVO: expiresAt-basiert |
| media (orphans) | 30 Tage | `RETENTION_MEDIA_ORPHAN_MIN_AGE_DAYS` | Unreferenzierte Medien |

### Automatischer Scheduler

Retention-Jobs laufen täglich um 03:00 Uhr (konfigurierbar via `RETENTION_CRON_SCHEDULE`).

```bash
# Umgebungsvariablen in .env
RETENTION_EMAIL_LOGS_DAYS=90
RETENTION_AUDIT_LOGS_DAYS=90
RETENTION_CONSENT_LOGS_DAYS=1095
RETENTION_MEDIA_ORPHAN_MIN_AGE_DAYS=30
RETENTION_CRON_SCHEDULE="0 3 * * *"

# Worker aktivieren/deaktivieren
QUEUE_ENABLE_RETENTION=true
QUEUE_ENABLE_RETENTION_SCHEDULER=true
```

### API-Endpoint `/api/retention`

**GET - Konfiguration abrufen:**
```bash
curl https://pl.porwoll.tech/api/retention \
  -H "Cookie: payload-token=..."
```

**GET - Job-Status abfragen:**
```bash
curl "https://pl.porwoll.tech/api/retention?jobId=abc123" \
  -H "Cookie: payload-token=..."
```

**POST - Manuellen Job auslösen:**
```bash
# Vollständige Retention (alle Policies + Media-Orphans)
curl -X POST https://pl.porwoll.tech/api/retention \
  -H "Content-Type: application/json" \
  -H "Cookie: payload-token=..." \
  -d '{"type": "full"}'

# Einzelne Collection bereinigen
curl -X POST https://pl.porwoll.tech/api/retention \
  -H "Content-Type: application/json" \
  -H "Cookie: payload-token=..." \
  -d '{"type": "collection", "collection": "email-logs"}'

# Nur Media-Orphans bereinigen
curl -X POST https://pl.porwoll.tech/api/retention \
  -H "Content-Type: application/json" \
  -H "Cookie: payload-token=..." \
  -d '{"type": "media-orphans"}'
```

### Architektur

```
Scheduler (Cron)
    ↓
Retention Queue (BullMQ)
    ↓
Retention Worker
    ↓
┌─────────────────┬─────────────────┬─────────────────┐
│  Email-Logs     │  Audit-Logs     │  Consent-Logs   │
│  (createdAt)    │  (createdAt)    │  (expiresAt)    │
└─────────────────┴─────────────────┴─────────────────┘
    ↓
Media-Orphan-Cleanup
    ↓
Cleanup-Ergebnis (Logs)
```

### Dateien

- `src/lib/retention/retention-config.ts` - Zentrale Konfiguration
- `src/lib/retention/cleanup-service.ts` - Lösch-Logik
- `src/lib/queue/jobs/retention-job.ts` - Job-Definition
- `src/lib/queue/workers/retention-worker.ts` - Worker
- `src/app/(payload)/api/retention/route.ts` - API-Endpoint

## Redis Caching

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

```typescript
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))
```

## Backup-System

Automatisches tägliches PostgreSQL-Backup mit lokalem Speicher und S3-Offsite-Backup.

**Setup auf neuem Server:**
```bash
cd /home/payload/payload-cms/scripts/backup
./setup-backup.sh
```

**Konfigurationsdateien (nicht im Repo):**
- `~/.pgpass` - PostgreSQL-Credentials (chmod 600)
- `~/.s3cfg` - S3-Credentials (chmod 600)

**Backup-Speicherorte:**
| Ort | Pfad | Retention |
|-----|------|-----------|
| Lokal | `/home/payload/backups/postgres/` | 30 Tage |
| S3 | `s3://c2s/backups/postgres/` | 30 Tage |

**Cron-Job:** Täglich um 03:00 Uhr

**Manuelles Backup:**
```bash
/home/payload/backups/postgres/backup-db.sh --verbose
```

**Restore aus Backup:**
```bash
# Lokal
gunzip -c /home/payload/backups/postgres/payload_db_YYYY-MM-DD_HH-MM-SS.sql.gz | \
  psql -h 10.10.181.101 -U payload -d payload_db

# Aus S3
s3cmd get s3://c2s/backups/postgres/payload_db_YYYY-MM-DD_HH-MM-SS.sql.gz
gunzip -c payload_db_*.sql.gz | psql -h 10.10.181.101 -U payload -d payload_db
```

Dokumentation: `scripts/backup/README.md`

## Datenbank-Direktzugriff

```bash
# Verwende ./scripts/db-direct.sh psql oder:
PGPASSWORD="$DB_PASSWORD" 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
```

## Blocks Übersicht (42 Blocks)

### Core Blocks
| Block | Slug | Beschreibung |
|-------|------|--------------|
| HeroBlock | hero-block | Einzelner Hero mit Bild, Headline, CTA |
| HeroSliderBlock | hero-slider-block | Hero-Slider mit mehreren Slides |
| ImageSliderBlock | image-slider-block | Bildergalerie/Karussell |
| TextBlock | text-block | Textinhalt mit Rich-Text |
| ImageTextBlock | image-text-block | Bild + Text nebeneinander |
| CardGridBlock | card-grid-block | Karten-Raster |
| QuoteBlock | quote-block | Zitat |
| CTABlock | cta-block | Call-to-Action |
| ContactFormBlock | contact-form-block | Kontaktformular |
| TimelineBlock | timeline-block | Timeline-Darstellung |
| DividerBlock | divider-block | Trennlinie |
| VideoBlock | video-block | Video einbetten |

### Content Blocks
| Block | Slug | Beschreibung |
|-------|------|--------------|
| PostsListBlock | posts-list-block | Beitrags-Liste |
| TestimonialsBlock | testimonials-block | Kundenbewertungen |
| NewsletterBlock | newsletter-block | Newsletter-Anmeldung |
| ProcessStepsBlock | process-steps-block | Prozess-Schritte |
| FAQBlock | faq-block | FAQ-Akkordeon |
| TeamBlock | team-block | Team-Mitglieder |
| ServicesBlock | services-block | Leistungen |

### Blogging Blocks
| Block | Slug | Beschreibung |
|-------|------|--------------|
| AuthorBioBlock | author-bio-block | Autoren-Biografie |
| RelatedPostsBlock | related-posts-block | Verwandte Beiträge |
| ShareButtonsBlock | share-buttons-block | Social Share Buttons |
| TableOfContentsBlock | table-of-contents-block | Inhaltsverzeichnis |

### Team Blocks
| Block | Slug | Beschreibung |
|-------|------|--------------|
| TeamFilterBlock | team-filter-block | Team mit Filter-Funktion |
| OrgChartBlock | org-chart-block | Organigramm |

### Feature Blocks
| Block | Slug | Beschreibung |
|-------|------|--------------|
| LocationsBlock | locations-block | Standorte/Filialen |
| LogoGridBlock | logo-grid-block | Partner/Kunden-Logos |
| StatsBlock | stats-block | Statistiken/Zahlen |
| JobsBlock | jobs-block | Stellenangebote |
| DownloadsBlock | downloads-block | Download-Bereich |
| MapBlock | map-block | Karten-Einbindung |

### Interactive Blocks
| Block | Slug | Beschreibung |
|-------|------|--------------|
| EventsBlock | events-block | Veranstaltungen |
| PricingBlock | pricing-block | Preistabellen |
| TabsBlock | tabs-block | Tab-Navigation |
| AccordionBlock | accordion-block | Akkordeon/Aufklappbar |
| ComparisonBlock | comparison-block | Vergleichstabelle |

### Tenant-specific Blocks
| Block | Slug | Beschreibung |
|-------|------|--------------|
| BeforeAfterBlock | before-after-block | Vorher/Nachher Bildvergleich (porwoll.de) |

### BlogWoman Blocks
| Block | Slug | Beschreibung |
|-------|------|--------------|
| FavoritesBlock | favorites-block | Affiliate-Produkte Grid/Liste/Karussell |
| SeriesBlock | series-block | YouTube-Serien Übersicht |
| SeriesDetailBlock | series-detail-block | Serien-Einzelseite mit Hero |
| VideoEmbedBlock | video-embed-block | YouTube/Vimeo Embed mit Privacy Mode |
| FeaturedContentBlock | featured-content-block | Kuratierte Mixed-Content Sammlung |

### HeroSliderBlock Features

Vollwertiger Hero-Slider mit:

**Slides (Array, 1-10):**
- Hintergrundbild (Desktop + optional Mobil)
- Headline + Subline (lokalisiert)
- Text-Ausrichtung (links/zentriert/rechts)
- Vertikale Position (oben/mitte/unten)
- Overlay (Farbe, Deckkraft, Gradient)
- Primärer + Sekundärer CTA-Button
- Textfarbe (weiß/dunkel/primär)

**Animationen:**
- Typen: fade, slide, zoom, flip, none
- Konfigurierbare Dauer (300-1200ms)

**Autoplay:**
- Intervall (3-10 Sekunden)
- Pause bei Hover
- Pause bei Interaktion

**Navigation:**
- Pfeile (verschiedene Stile, Positionen)
- Dots (Punkte, Striche, Nummern, Thumbnails, Fortschritt)
- Touch-Swipe
- Tastaturnavigation

**Layout:**
- Höhe (Vollbild bis kompakt)
- Separate Mobile-Höhe
- Content-Breite

## Collections Übersicht (40+ Collections)

### Core Collections
| 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 |

### Content Collections
| Collection | Slug | Beschreibung |
|------------|------|--------------|
| Posts | posts | Blog/News/Presse mit Kategorien |
| Categories | categories | Kategorien für Posts |
| Tags | tags | Tags für Posts (Blogging) |
| Authors | authors | Autoren für Posts |
| Testimonials | testimonials | Kundenbewertungen |
| FAQs | faqs | Häufig gestellte Fragen (FAQ) |
| SocialLinks | social-links | Social Media Links |

### Team & Services
| Collection | Slug | Beschreibung |
|------------|------|--------------|
| Team | team | Team-Mitglieder und Mitarbeiter |
| ServiceCategories | service-categories | Kategorien für Leistungen |
| Services | services | Leistungen und Dienstleistungen |
| Jobs | jobs | Stellenangebote |

### Portfolio & Media
| Collection | Slug | Beschreibung |
|------------|------|--------------|
| Portfolios | portfolios | Portfolio-Galerien (Fotografie) |
| PortfolioCategories | portfolio-categories | Kategorien für Portfolios |
| Videos | videos | Video-Bibliothek mit YouTube/Vimeo/Uploads |
| VideoCategories | video-categories | Kategorien für Videos |

### Products (E-Commerce)
| Collection | Slug | Beschreibung |
|------------|------|--------------|
| Products | products | Produkte |
| ProductCategories | product-categories | Produkt-Kategorien |

### Feature Collections
| Collection | Slug | Beschreibung |
|------------|------|--------------|
| Locations | locations | Standorte/Filialen |
| Partners | partners | Partner/Kunden |
| Downloads | downloads | Download-Dateien |
| Events | events | Veranstaltungen |
| Timelines | timelines | Chronologische Events (Geschichte, Meilensteine) |
| Workflows | workflows | Komplexe Prozesse mit Phasen und Schritten |

### Formulare & Newsletter
| Collection | Slug | Beschreibung |
|------------|------|--------------|
| Forms | forms | Formular-Builder (Plugin) |
| FormSubmissions | form-submissions | Formular-Einsendungen mit CRM-Workflow |
| NewsletterSubscribers | newsletter-subscribers | Newsletter mit Double Opt-In |

### Consent & Privacy (DSGVO)
| Collection | Slug | Beschreibung |
|------------|------|--------------|
| CookieConfigurations | cookie-configurations | Cookie-Banner Konfiguration |
| CookieInventory | cookie-inventory | Cookie-Inventar |
| ConsentLogs | consent-logs | Consent-Protokollierung (WORM) |
| PrivacyPolicySettings | privacy-policy-settings | Datenschutz-Einstellungen |

### Tenant-specific Collections
| Collection | Slug | Beschreibung |
|------------|------|--------------|
| Bookings | bookings | Fotografie-Buchungen (porwoll.de) |
| Certifications | certifications | Zertifizierungen (C2S) |
| Projects | projects | Game-Development-Projekte (gunshin.de) |

### BlogWoman Collections
| Collection | Slug | Beschreibung |
|------------|------|--------------|
| Favorites | favorites | Affiliate-Produkte mit Kategorien und Badges |
| Series | series | YouTube-Serien mit Branding (Logo, Farben) |

### System Collections
| Collection | Slug | Beschreibung |
|------------|------|--------------|
| EmailLogs | email-logs | E-Mail-Protokollierung |
| AuditLogs | audit-logs | Security Audit Trail |
| SiteSettings | site-settings | Website-Einstellungen (pro Tenant) |
| Navigations | navigations | Navigationsmenüs (pro Tenant) |
| Redirects | redirects | URL-Weiterleitungen (Plugin) |

## Timeline Collection

Dedizierte Collection für komplexe chronologische Darstellungen:

**Timeline-Typen:**
- `history` - Unternehmensgeschichte
- `milestones` - Projektmeilensteine
- `releases` - Produkt-Releases
- `career` - Karriere/Lebenslauf
- `events` - Ereignisse
- `process` - Prozess/Ablauf

**Event-Features:**
- Flexible Datumsformate (Jahr, Monat+Jahr, vollständig, Zeitraum, Freitext)
- Kategorien (Meilenstein, Gründung, Produkt, Team, Auszeichnung, etc.)
- Wichtigkeitsstufen (Highlight, Normal, Minor)
- Bilder und Galerien
- Links und Metadaten
- Rich-Text-Beschreibungen

**Display-Optionen:**
- Layouts: vertikal, alternierend, horizontal, kompakt
- Sortierung: aufsteigend/absteigend
- Gruppierung nach Jahr
- Verschiedene Marker-Stile

**Prozess-spezifische Felder (type=process):**
- `stepNumber` - Explizite Schritt-Nummerierung
- `duration` - Dauer (z.B. "2-3 Tage")
- `responsible` - Verantwortliche Person/Rolle
- `actionRequired` - Wer muss aktiv werden (customer, internal, both, automatic)
- `deliverables` - Ergebnisse/Dokumente des Schritts

**API-Endpoint:**
```bash
# Liste aller Timelines eines Tenants
curl "https://pl.porwoll.tech/api/timelines?tenant=1"

# Nach Typ filtern
curl "https://pl.porwoll.tech/api/timelines?tenant=1&type=history"

# Einzelne Timeline
curl "https://pl.porwoll.tech/api/timelines?tenant=1&slug=company-history"

# Mit Sprache
curl "https://pl.porwoll.tech/api/timelines?tenant=1&locale=en"
```

## Workflows Collection

Komplexe Prozess- und Workflow-Darstellungen mit Phasen, Abhängigkeiten und Status-Tracking.

**Workflow-Typen:**
- `project` - Projektabläufe
- `business` - Geschäftsprozesse
- `approval` - Genehmigungs-Workflows
- `onboarding` - Mitarbeiter-/Kundeneinführung
- `support` - Support/Service-Prozesse
- `development` - Entwicklungsprozesse
- `marketing` - Marketing-Workflows
- `other` - Sonstige

**Eigenschaften:**
- `estimatedDuration` - Geschätzte Gesamtdauer
- `complexity` - simple, medium, complex, very_complex
- `isIterative` - Kann wiederholt werden
- `allowParallelPhases` - Parallele Phasen möglich

**Display-Optionen:**
- Layouts: vertical, horizontal, flowchart, kanban, gantt
- Farbschema: phase, status, priority, brand
- Optionale Anzeige: Nummern, Zeiten, Verantwortliche, Fortschritt

**Phasen-Struktur:**
```
Workflow
└── Phasen (Array)
    ├── name, description, icon, color
    ├── estimatedDuration, responsible
    ├── deliverables (Array)
    └── Schritte (Array)
        ├── name, description, stepType, priority
        ├── estimatedDuration, responsible
        ├── dependencies (dependsOnSteps, canRunParallel, isBlocking)
        ├── conditions (für Entscheidungen)
        ├── checklist (Array)
        ├── resources (Dokumente, Links, Tools)
        └── outputs (Ergebnisse)
```

**API-Endpoint:**
```bash
# Liste aller Workflows eines Tenants
curl "https://pl.porwoll.tech/api/workflows?tenant=1"

# Nach Typ filtern
curl "https://pl.porwoll.tech/api/workflows?tenant=1&type=project"

# Nach Komplexität filtern
curl "https://pl.porwoll.tech/api/workflows?tenant=1&complexity=medium"

# Einzelner Workflow
curl "https://pl.porwoll.tech/api/workflows?tenant=1&slug=web-project"

# Mit Sprache
curl "https://pl.porwoll.tech/api/workflows?tenant=1&locale=en"
```

**Validierung:**
- `tenant` - Pflichtparameter (Tenant-Isolation)
- `type` - Validiert gegen erlaubte Typen (400 bei Fehler)
- `complexity` - Validiert gegen erlaubte Werte (400 bei Fehler)
- `locale` - de (default) oder en

## FormSubmissions CRM-Workflow

Die FormSubmissions Collection wurde zu einem leichtgewichtigen CRM erweitert:

**Status-Workflow:**
- 🆕 Neu → 👀 Gelesen → 🔄 In Bearbeitung → ⏳ Warten → ✅ Erledigt → 🗑️ Archiviert

**Features:**
- Priorität (Hoch/Normal/Niedrig)
- Zuständigkeits-Zuweisung an User
- Interne Notizen mit Auto-Autor und Zeitstempel
- Antwort-Tracking (Methode, Zeitstempel, Zusammenfassung)
- Tags zur Kategorisierung
- Auto-Markierung als gelesen beim ersten Öffnen

**Dateien:**
- `src/collections/FormSubmissionsOverrides.ts` - Feld-Definitionen
- `src/hooks/formSubmissionHooks.ts` - Automatisierungen

## Globals

> **Hinweis:** SiteSettings, Navigations und PrivacyPolicySettings wurden zu tenant-spezifischen Collections umgewandelt (siehe Collections Übersicht).

| Global | Slug | Beschreibung |
|--------|------|--------------|
| SEOSettings | seo-settings | Globale SEO-Einstellungen (systemweit) |

## Test Suite

```bash
# 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%

## CI/CD Pipeline

GitHub Actions Workflows in `.github/workflows/`:

### ci.yml (Main CI Pipeline)
Läuft bei Push/PR auf `main` und `develop`:

| Job | Beschreibung |
|-----|--------------|
| **lint** | ESLint + Prettier Check |
| **typecheck** | TypeScript Compiler (`tsc --noEmit`) |
| **test** | Unit & Integration Tests (Vitest) |
| **build** | Next.js Production Build |
| **e2e** | Playwright E2E Tests |

**Lokale Ausführung:**
```bash
pnpm lint              # ESLint
pnpm typecheck         # TypeScript Check
pnpm format:check      # Prettier Check
pnpm format            # Prettier Auto-Fix
pnpm test              # Alle Tests
pnpm build             # Production Build
```

### security.yml (Security Scanning)
- **Gitleaks**: Secret Scanning
- **pnpm audit**: Dependency Vulnerabilities
- **CodeQL**: Static Analysis (SAST)
- **Security Tests**: Unit & Integration Tests für Security-Module

### deploy-staging.yml (Staging Deployment)
Automatisches Deployment auf Staging-Server bei Push auf `develop`:

| Trigger | Aktion |
|---------|--------|
| Push auf `develop` | Automatisches Deployment |
| `workflow_dispatch` | Manuelles Deployment (optional: skip_tests) |

**Deployment-Ziel:**
- **URL:** https://pl.porwoll.tech
- **Server:** 37.24.237.181 (sv-payload)

**Ablauf:**
1. Pre-deployment Checks (Lint, Tests)
2. SSH-Verbindung zum Staging-Server
3. Git Pull + Dependencies installieren
4. Migrations ausführen
5. Build + PM2 Restart
6. Health Check

**Manuelles Staging-Deployment:**
```bash
# Auf dem Staging-Server (pl.porwoll.tech)
./scripts/deploy-staging.sh

# Mit Optionen
./scripts/deploy-staging.sh --skip-build      # Nur Code-Update
./scripts/deploy-staging.sh --skip-migrations # Ohne Migrationen
```

**GitHub Secret erforderlich:**
- `STAGING_SSH_KEY` - SSH Private Key für `payload@37.24.237.181`

### deploy-production.yml (Production Deployment)
Manuelles Deployment auf Production-Server (Hetzner 3) bei workflow_dispatch:

| Feature | Beschreibung |
|---------|--------------|
| Pre-flight Checks | Überprüft Branch-Status und Migrationen |
| Pre-deployment Tests | Optional, Lint + Unit Tests |
| Database Backup | Automatisches Backup vor Deployment |
| Health Check | Verifiziert erfolgreiche Deployment |
| Auto-Rollback | Bei fehlgeschlagenem Health Check |

**Manuelles Production-Deployment:**
```bash
# Via GitHub Actions (empfohlen)
gh workflow run deploy-production.yml

# Auf dem Production-Server (Hetzner 3)
ssh payload@162.55.85.18
./scripts/deploy-production.sh

# Mit Optionen
./scripts/deploy-production.sh -y              # Ohne Bestätigung
./scripts/deploy-production.sh --skip-backup   # Ohne Backup
./scripts/deploy-production.sh --rollback      # Rollback zur vorherigen Version
./scripts/deploy-production.sh --dry-run       # Zeigt was passieren würde
```

**GitHub Secrets erforderlich:**
- `STAGING_SSH_KEY` - SSH Private Key für sv-payload
- `PRODUCTION_SSH_KEY` - SSH Private Key für Hetzner 3

## Dokumentation

### Hauptdokumentation
- `CLAUDE.md` - Diese Datei (Projekt-Übersicht für AI-Assistenten)
- `docs/INFRASTRUCTURE.md` - Infrastruktur-Übersicht (Dev + Prod)
- `docs/DEPLOYMENT.md` - Deployment-Prozess & Checklisten
- `docs/DEPLOYMENT_STRATEGY.md` - Vollständige Deployment-Strategie (Dev → Prod)
- `docs/PROJECT_STATUS.md` - Aktueller Projektstatus & Roadmap
- `docs/STAGING-DEPLOYMENT.md` - Staging Deployment Workflow

### Anleitungen
- `docs/anleitungen/TODO.md` - Task-Liste & Changelog
- `docs/anleitungen/SECURITY.md` - Sicherheitsrichtlinien
- `docs/anleitungen/FRONTEND.md` - Frontend-Entwicklung (sv-frontend)
- `docs/anleitungen/API_ANLEITUNG.md` - API-Dokumentation
- `docs/anleitungen/framework-monitoring.md` - Framework-Updates beobachten

### Scripts & Backup
- `scripts/backup/README.md` - Backup-System Dokumentation

*Letzte Aktualisierung: 09.01.2026*
