mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 18:34:13 +00:00
The manual sync button in Community Inbox was only syncing YouTube comments via the legacy syncAllComments service. Now uses UnifiedSyncService to sync all platforms (YouTube, Facebook, Instagram). Also adds comprehensive Community Management documentation to CLAUDE.md. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1492 lines
53 KiB
Markdown
1492 lines
53 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.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 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
|
|
|
|
# Meta (Facebook + Instagram) OAuth
|
|
META_APP_ID=your-app-id # Facebook App ID
|
|
META_APP_SECRET=your-app-secret # Facebook App Secret
|
|
META_REDIRECT_URI=https://your-domain/api/auth/meta/callback
|
|
|
|
# YouTube OAuth (existing)
|
|
GOOGLE_CLIENT_ID=your-client-id # Google Cloud Console
|
|
GOOGLE_CLIENT_SECRET=your-client-secret
|
|
YOUTUBE_REDIRECT_URI=https://your-domain/api/youtube/callback
|
|
|
|
# Cron Jobs
|
|
CRON_SECRET=your-64-char-hex # Auth für Cron-Endpoints
|
|
```
|
|
|
|
## 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
|
|
|
|
# Backup
|
|
/home/payload/backups/postgres/backup-db.sh --verbose # Manuelles Backup
|
|
scripts/backup/setup-backup.sh # Backup-System einrichten
|
|
```
|
|
|
|
## Git Branching Workflow
|
|
|
|
**Wichtig:** Immer zuerst auf `develop` entwickeln, nach Freigabe mit `main` mergen.
|
|
|
|
```
|
|
develop ──────●────●────●────────●────── (Entwicklung)
|
|
\ /
|
|
main ────────●────────────●────────── (Production)
|
|
```
|
|
|
|
### Branches
|
|
|
|
| Branch | Zweck | Deployment |
|
|
|--------|-------|------------|
|
|
| `develop` | Aktive Entwicklung | Staging (pl.porwoll.tech) |
|
|
| `main` | Stabile Version | Production (cms.c2sgmbh.de) |
|
|
| `feature/*` | Feature-Branches | Lokal / PR nach develop |
|
|
|
|
### Entwicklungs-Workflow
|
|
|
|
1. **Auf develop arbeiten:**
|
|
```bash
|
|
git checkout develop
|
|
git pull origin develop
|
|
```
|
|
|
|
2. **Änderungen committen:**
|
|
```bash
|
|
git add .
|
|
git commit -m "feat/fix/docs: beschreibung"
|
|
git push origin develop
|
|
```
|
|
|
|
3. **Nach Freigabe: develop → main mergen:**
|
|
```bash
|
|
git checkout main
|
|
git pull origin main
|
|
git merge develop
|
|
git push origin main
|
|
```
|
|
|
|
4. **develop aktuell halten (falls main Hotfixes hat):**
|
|
```bash
|
|
git checkout develop
|
|
git merge main
|
|
git push origin develop
|
|
```
|
|
|
|
### Commit-Konventionen
|
|
|
|
| Prefix | Verwendung |
|
|
|--------|------------|
|
|
| `feat:` | Neue Features |
|
|
| `fix:` | Bug-Fixes |
|
|
| `docs:` | Dokumentation |
|
|
| `refactor:` | Code-Refactoring |
|
|
| `test:` | Tests |
|
|
| `chore:` | Maintenance |
|
|
|
|
---
|
|
|
|
## Workflow nach Code-Änderungen
|
|
|
|
1. Code ändern (auf `develop` Branch)
|
|
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
|
|
- **Queue Worker:** Benötigt `tsx` als explizite devDependency (für TypeScript-Ausführung via PM2)
|
|
|
|
## 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:** `_locales` Tabellen für lokalisierte Felder
|
|
- **API:** `?locale=de` oder `?locale=en` Parameter
|
|
- **Frontend:** Routing über `/[locale]/...`
|
|
|
|
## 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)
|
|
- **YouTube Analytics:** https://pl.porwoll.tech/admin/youtube-analytics (Admin View, Auth erforderlich)
|
|
- **Community Inbox:** https://pl.porwoll.tech/admin/community/inbox (Admin View, Auth erforderlich)
|
|
- **Community Analytics:** https://pl.porwoll.tech/admin/community/analytics (Admin View, Auth erforderlich)
|
|
- **Community Sync:** https://pl.porwoll.tech/api/community/sync (POST: Trigger, GET: Status)
|
|
- **Community Stats:** https://pl.porwoll.tech/api/community/stats (GET, Auth erforderlich)
|
|
- **Meta OAuth:** https://pl.porwoll.tech/api/auth/meta (GET, startet OAuth-Flow)
|
|
|
|
## 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 (43 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 |
|
|
| ScriptSectionBlock | script-section-block | YouTube-Script Abschnitte |
|
|
|
|
### 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 (55+ 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) |
|
|
|
|
### YouTube & Community Management
|
|
| Collection | Slug | Beschreibung |
|
|
|------------|------|--------------|
|
|
| YouTubeChannels | youtube-channels | Multi-Kanal-Verwaltung mit OAuth |
|
|
| YouTubeContent | youtube-content | Videos + Shorts mit Kommentaren |
|
|
| YtSeries | yt-series | Serien mit Branding (Logo, Farben, Playlist) |
|
|
| YtNotifications | yt-notifications | Handlungsbedarf-System |
|
|
| YtScriptTemplates | yt-script-templates | Skript-Vorlagen für Videos |
|
|
| YtBatches | yt-batches | Video-Batches für Produktions-Planung |
|
|
| YtChecklistTemplates | yt-checklist-templates | Checklisten-Vorlagen für Video-Produktion |
|
|
| YtMonthlyGoals | yt-monthly-goals | Monatliche YouTube-Ziele |
|
|
| YtTasks | yt-tasks | YouTube-Aufgaben-Verwaltung |
|
|
| SocialAccounts | social-accounts | Meta/Social OAuth-Verbindungen |
|
|
| SocialPlatforms | social-platforms | Plattform-Konfigurationen |
|
|
| CommunityInteractions | community-interactions | Kommentare & Interaktionen |
|
|
| CommunityRules | community-rules | Automatische Antwort-Regeln |
|
|
| CommunityTemplates | community-templates | Antwort-Vorlagen |
|
|
| ReportSchedules | report-schedules | Geplante Community-Reports |
|
|
|
|
### 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
|
|
|
|
## Community Management System
|
|
|
|
Plattformübergreifendes Community Management für YouTube, Facebook und Instagram mit einer einheitlichen Inbox, AI-Analyse und automatischer Moderation.
|
|
|
|
### Architektur
|
|
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ Admin UI │
|
|
│ ┌─────────────┐ ┌──────────────────┐ │
|
|
│ │ Community │ │ Community │ │
|
|
│ │ Inbox │ │ Analytics │ │
|
|
│ └──────┬──────┘ └────────┬─────────┘ │
|
|
└─────────┼──────────────────┼────────────┘
|
|
│ │
|
|
┌─────────▼──────────────────▼────────────┐
|
|
│ /api/community/* │
|
|
│ sync, sync-status, stats, reply, │
|
|
│ generate-reply, export, stream │
|
|
└─────────────────┬───────────────────────┘
|
|
│
|
|
┌─────────────────▼───────────────────────┐
|
|
│ UnifiedSyncService │
|
|
│ ┌──────────┬───────────┬────────────┐ │
|
|
│ │ YouTube │ Facebook │ Instagram │ │
|
|
│ │ Sync │ Sync │ Sync │ │
|
|
│ └────┬─────┴─────┬─────┴──────┬─────┘ │
|
|
└───────┼───────────┼────────────┼────────┘
|
|
│ │ │
|
|
┌───────▼───────────▼────────────▼────────┐
|
|
│ CommunityInteractions │
|
|
│ (einheitliche Collection für alle │
|
|
│ Plattform-Kommentare) │
|
|
└─────────────────────────────────────────┘
|
|
```
|
|
|
|
### Admin UI
|
|
|
|
**Community Inbox** (`/admin/community/inbox`):
|
|
- Einheitliche Ansicht aller Kommentare (YouTube + Facebook + Instagram)
|
|
- Filter nach Plattform, Status, Priorität, Sentiment, Flags
|
|
- AI-generierte Antwort-Vorschläge (Claude)
|
|
- Direkte Antwort-Funktion
|
|
- Echtzeit-Updates via SSE (`/api/community/stream`)
|
|
- Export als CSV/JSON
|
|
- Sync-Button (triggert alle Plattformen)
|
|
|
|
**Community Analytics** (`/admin/community/analytics`):
|
|
- KPI-Cards (Gesamt, Heute, Unbeantwortet)
|
|
- Sentiment-Trend-Chart
|
|
- Topic-Cloud
|
|
- Response-Metriken
|
|
- Kanal-Vergleich
|
|
- Top-Content nach Engagement
|
|
|
|
### Meta (Facebook + Instagram) Integration
|
|
|
|
**OAuth-Flow:**
|
|
```
|
|
1. Admin öffnet /api/auth/meta?socialAccountId=X&accountType=both
|
|
2. Redirect zu Facebook OAuth (12 Scopes)
|
|
3. Callback: Short-Lived → Long-Lived Token (60 Tage)
|
|
4. Facebook Pages + Instagram Business Accounts werden geladen
|
|
5. Token + Account-Daten in SocialAccounts gespeichert
|
|
```
|
|
|
|
**Unterstützte Scopes:**
|
|
- Facebook: `pages_show_list`, `pages_read_engagement`, `pages_manage_posts`, `pages_read_user_content`, `pages_manage_engagement`, `pages_messaging`
|
|
- Instagram: `instagram_basic`, `instagram_manage_comments`, `instagram_manage_messages`, `instagram_content_publish`
|
|
|
|
**API-Clients:**
|
|
|
|
| Client | Datei | Funktionen |
|
|
|--------|-------|------------|
|
|
| MetaBaseClient | `src/lib/integrations/meta/MetaBaseClient.ts` | HTTP-Basis, Pagination, Retry, Token-Management |
|
|
| FacebookClient | `src/lib/integrations/meta/FacebookClient.ts` | Posts, Kommentare, Messenger, Insights |
|
|
| InstagramClient | `src/lib/integrations/meta/InstagramClient.ts` | Media, Kommentare, Mentions, DMs, Insights |
|
|
|
|
**Sync-Services:**
|
|
|
|
| Service | Datei | Beschreibung |
|
|
|---------|-------|--------------|
|
|
| FacebookSyncService | `src/lib/integrations/meta/FacebookSyncService.ts` | Synct Page-Kommentare + Replies |
|
|
| InstagramSyncService | `src/lib/integrations/meta/InstagramSyncService.ts` | Synct Media-Kommentare + Mentions |
|
|
| UnifiedSyncService | `src/lib/jobs/UnifiedSyncService.ts` | Orchestriert YouTube + Facebook + Instagram |
|
|
|
|
**Sync-Ablauf (pro Account):**
|
|
1. SocialAccount laden + Platform verifizieren
|
|
2. Token validieren
|
|
3. Posts/Media abrufen (mit Datum-Filter)
|
|
4. Kommentare pro Post synchronisieren
|
|
5. AI-Analyse via Claude (Sentiment, Topics, Flags)
|
|
6. In CommunityInteractions speichern (dedupliziert via `externalId`)
|
|
7. Account-Stats aktualisieren
|
|
|
|
### Sync-Trigger
|
|
|
|
| Trigger | Endpoint | Plattformen |
|
|
|---------|----------|-------------|
|
|
| Cron (alle 15 Min) | `/api/cron/community-sync` | Alle aktiven Accounts |
|
|
| Manuell (Inbox-Button) | `/api/community/sync` (POST) | Alle (optional filterbar) |
|
|
| YouTube-spezifisch | `/api/cron/youtube-sync` | Nur YouTube |
|
|
|
|
### Community API-Endpoints
|
|
|
|
| Endpoint | Methode | Beschreibung |
|
|
|----------|---------|--------------|
|
|
| `/api/community/sync` | POST | Manueller Sync (alle Plattformen) |
|
|
| `/api/community/sync` | GET | Sync-Status |
|
|
| `/api/community/sync-status` | GET | Detaillierter Status mit Account-Stats |
|
|
| `/api/community/stats` | GET | Interaktions-Statistiken |
|
|
| `/api/community/reply` | POST | Antwort senden |
|
|
| `/api/community/generate-reply` | POST | AI-Antwort generieren (Claude) |
|
|
| `/api/community/export` | GET | Daten-Export (CSV/JSON) |
|
|
| `/api/community/stream` | GET | SSE-Stream für Echtzeit-Updates |
|
|
| `/api/community/sync-comments` | POST | Kommentar-Sync (Legacy) |
|
|
| `/api/community/analytics/*` | GET | Analytics-Daten (6 Endpoints) |
|
|
| `/api/auth/meta` | GET | Meta OAuth starten |
|
|
| `/api/auth/meta/callback` | GET | Meta OAuth Callback |
|
|
|
|
### CommunityRules (Automatisierung)
|
|
|
|
Regel-basierte Automatisierung für eingehende Kommentare:
|
|
|
|
**Trigger-Typen:** `keyword`, `sentiment`, `question_detected`, `medical_detected`, `influencer` (>10k Follower), `all_new`, `contains_link`, `contains_email`
|
|
|
|
**Aktionen:** `set_priority`, `assign_to`, `set_flag`, `suggest_template`, `send_notification`, `flag_medical`, `escalate`, `mark_spam`, `set_deadline`
|
|
|
|
### Wichtige Dateien
|
|
|
|
```
|
|
src/lib/integrations/meta/
|
|
├── MetaBaseClient.ts # HTTP-Basis mit Retry + Pagination
|
|
├── FacebookClient.ts # Facebook Graph API Client
|
|
├── InstagramClient.ts # Instagram Graph API Client
|
|
├── FacebookSyncService.ts # Facebook Kommentar-Sync
|
|
├── InstagramSyncService.ts # Instagram Kommentar-Sync
|
|
├── oauth.ts # Meta OAuth 2.0 Flow
|
|
└── index.ts # Re-Exports
|
|
|
|
src/lib/jobs/
|
|
├── UnifiedSyncService.ts # Orchestriert alle Plattform-Syncs
|
|
├── syncAllComments.ts # YouTube-spezifischer Sync (Legacy)
|
|
└── JobLogger.ts # Strukturiertes Logging
|
|
|
|
src/app/(payload)/api/
|
|
├── auth/meta/ # OAuth Start + Callback
|
|
├── community/ # 14 Community-Endpoints
|
|
└── cron/community-sync/ # Cron-Trigger für Unified Sync
|
|
```
|
|
|
|
## 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%
|
|
|
|
## Scheduled Cron Jobs (Vercel)
|
|
|
|
Automatische Hintergrund-Jobs via Vercel Cron (`vercel.json`):
|
|
|
|
| Endpoint | Schedule | Beschreibung |
|
|
|----------|----------|--------------|
|
|
| `/api/cron/community-sync` | `*/15 * * * *` (alle 15 Min) | Synchronisiert Kommentare von YouTube, Facebook, Instagram |
|
|
| `/api/cron/token-refresh` | `0 6,18 * * *` (6:00 + 18:00 UTC) | Erneuert ablaufende OAuth-Tokens automatisch |
|
|
| `/api/cron/send-reports` | `0 * * * *` (stündlich) | Versendet fällige Community-Reports per E-Mail |
|
|
|
|
**Authentifizierung:**
|
|
Alle Cron-Endpoints erfordern `Authorization: Bearer $CRON_SECRET` Header.
|
|
|
|
**Manueller Trigger:**
|
|
```bash
|
|
# Community Sync manuell auslösen
|
|
curl -X POST "https://your-domain/api/cron/community-sync" \
|
|
-H "Authorization: Bearer $CRON_SECRET" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"platforms": ["youtube", "facebook"]}'
|
|
|
|
# Token Refresh manuell auslösen (Dry-Run)
|
|
curl "https://your-domain/api/cron/token-refresh?dryRun=true" \
|
|
-H "Authorization: Bearer $CRON_SECRET"
|
|
|
|
# Token Refresh Status prüfen
|
|
curl -I "https://your-domain/api/cron/token-refresh" \
|
|
-H "Authorization: Bearer $CRON_SECRET"
|
|
```
|
|
|
|
**Monitoring:**
|
|
- HEAD-Requests geben Status-Header zurück (`X-Sync-Running`, `X-Last-Run`, etc.)
|
|
- Bei laufendem Job: HTTP 423 (Locked)
|
|
- Fehlerhafte Tokens werden als Benachrichtigungen in `yt-notifications` gespeichert
|
|
|
|
## 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 (flat config, 0 errors) |
|
|
| **typecheck** | TypeScript Compiler (`tsc --noEmit`, 4GB heap) |
|
|
| **test** | Unit & Integration Tests (Vitest) |
|
|
| **build** | Next.js Production Build |
|
|
| **e2e** | Playwright E2E Tests |
|
|
|
|
**Lokale Ausführung:**
|
|
```bash
|
|
pnpm lint # ESLint (0 errors, warnings only)
|
|
pnpm typecheck # TypeScript Check (4GB heap for 55+ collections)
|
|
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: ESLint, Unit Tests, Build |
|
|
| Database Backup | Automatisches Backup vor Deployment |
|
|
| Health Check | Admin Panel (200) + API (< 500) |
|
|
| Post-deployment Verification | Admin + API Health Check |
|
|
| 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: 13.02.2026 (Community Management Doku, Sync-Endpoint Fix: UnifiedSyncService statt YouTube-only)*
|