From dcfc48f5ce0adb8da1edd15b65bae509ced1aad4 Mon Sep 17 00:00:00 2001 From: CCS Admin Date: Tue, 20 Jan 2026 21:24:13 +0000 Subject: [PATCH] Add documentation and BlogWoman frontend development prompt - Add API documentation (API_ANLEITUNG.md) - Add architecture docs (UNIVERSAL_FEATURES.md, Analytics.md) - Add guides (FRONTEND.md, SEO_ERWEITERUNG.md, styleguide.md) - Add Planungs-KI prompt template (ANLEITUNG-PLANUNGS-KI-FRONTEND.md) - Add BlogWoman frontend development prompt with: - Tenant-ID 9 configuration - Design system based on styleguide - BlogWoman-specific blocks (Favorites, Series, VideoEmbed) - API patterns with tenant isolation - SEO and Analytics integration Co-Authored-By: Claude Opus 4.5 --- docs/api/API_ANLEITUNG.md | 1289 ++ docs/architecture/Analytics.md | 981 ++ docs/architecture/UNIVERSAL_FEATURES.md | 758 + docs/components/payload-types.ts | 12784 ++++++++++++++++ docs/guides/FRONTEND.md | 526 + docs/guides/KONZEPT-KI-ANLEITUNG.md | 805 + docs/guides/SEO_ERWEITERUNG.md | 378 + docs/guides/styleguide.md | 2145 +++ ...26-01-20_blogwoman-frontend-entwicklung.md | 1114 ++ prompts/ANLEITUNG-PLANUNGS-KI-FRONTEND.md | 730 + 10 files changed, 21510 insertions(+) create mode 100644 docs/api/API_ANLEITUNG.md create mode 100644 docs/architecture/Analytics.md create mode 100644 docs/architecture/UNIVERSAL_FEATURES.md create mode 100644 docs/components/payload-types.ts create mode 100644 docs/guides/FRONTEND.md create mode 100644 docs/guides/KONZEPT-KI-ANLEITUNG.md create mode 100644 docs/guides/SEO_ERWEITERUNG.md create mode 100644 docs/guides/styleguide.md create mode 100644 prompts/2026-01-20_blogwoman-frontend-entwicklung.md create mode 100644 prompts/ANLEITUNG-PLANUNGS-KI-FRONTEND.md diff --git a/docs/api/API_ANLEITUNG.md b/docs/api/API_ANLEITUNG.md new file mode 100644 index 0000000..7b3f4fe --- /dev/null +++ b/docs/api/API_ANLEITUNG.md @@ -0,0 +1,1289 @@ +# API-Anleitung - Payload CMS + +## Übersicht + +Das Payload CMS stellt eine REST-API und eine GraphQL-API bereit. Diese Anleitung beschreibt die Nutzung der REST-API für alle Collections. + +**Base URL:** `https://pl.porwoll.tech/api` + +--- + +## Authentifizierung + +### Login + +```bash +curl -X POST "https://pl.porwoll.tech/api/users/login" \ + -H "Content-Type: application/json" \ + -d '{ + "email": "admin@example.com", + "password": "your-password" + }' +``` + +**Response:** +```json +{ + "message": "Auth Passed", + "user": { + "id": 1, + "email": "admin@example.com", + "isSuperAdmin": true, + "tenants": [...] + }, + "token": "eyJhbGciOiJIUzI1NiIs..." +} +``` + +### Token verwenden + +```bash +curl "https://pl.porwoll.tech/api/posts" \ + -H "Authorization: JWT eyJhbGciOiJIUzI1NiIs..." +``` + +--- + +## Mehrsprachigkeit (Localization) + +Das CMS unterstützt Deutsch (de) und Englisch (en). Lokalisierte Felder können über den `locale` Parameter abgerufen werden. + +```bash +# Deutsche Inhalte (Standard) +curl "https://pl.porwoll.tech/api/posts?locale=de" + +# Englische Inhalte +curl "https://pl.porwoll.tech/api/posts?locale=en" + +# Alle Sprachen gleichzeitig +curl "https://pl.porwoll.tech/api/posts?locale=all" +``` + +--- + +## Users API + +### Aktuellen User abrufen + +```bash +curl "https://pl.porwoll.tech/api/users/me" \ + -H "Authorization: JWT your-token" +``` + +### User-Felder + +| Feld | Typ | Beschreibung | +|------|-----|--------------| +| `email` | string | E-Mail-Adresse (eindeutig) | +| `isSuperAdmin` | boolean | Super Admin hat Zugriff auf alle Tenants | +| `tenants` | array | Zugewiesene Tenants | + +--- + +## Tenants API + +### Alle Tenants abrufen (Auth + SuperAdmin erforderlich) + +```bash +curl "https://pl.porwoll.tech/api/tenants" \ + -H "Authorization: JWT your-token" +``` + +### Tenant erstellen (Auth + SuperAdmin erforderlich) + +```bash +curl -X POST "https://pl.porwoll.tech/api/tenants" \ + -H "Authorization: JWT your-token" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Meine Firma GmbH", + "slug": "meine-firma", + "domains": [ + { "domain": "meine-firma.de" }, + { "domain": "www.meine-firma.de" } + ] + }' +``` + +### Tenant-Felder + +| Feld | Typ | Beschreibung | +|------|-----|--------------| +| `name` | string | Anzeigename des Tenants | +| `slug` | string | URL-freundlicher Identifier (eindeutig) | +| `domains` | array | Zugeordnete Domains | + +--- + +## Posts API + +### Alle Posts abrufen + +```bash +# Alle Posts +curl "https://pl.porwoll.tech/api/posts" + +# Nur Blog-Artikel +curl "https://pl.porwoll.tech/api/posts?where[type][equals]=blog" + +# Nur News +curl "https://pl.porwoll.tech/api/posts?where[type][equals]=news" + +# Nur veröffentlichte Posts +curl "https://pl.porwoll.tech/api/posts?where[status][equals]=published" + +# Nur hervorgehobene Posts +curl "https://pl.porwoll.tech/api/posts?where[isFeatured][equals]=true" + +# Mit Sortierung (neueste zuerst) +curl "https://pl.porwoll.tech/api/posts?sort=-publishedAt" + +# Limitiert auf 10 Einträge +curl "https://pl.porwoll.tech/api/posts?limit=10" + +# Pagination (Seite 2) +curl "https://pl.porwoll.tech/api/posts?limit=10&page=2" + +# Mit Locale +curl "https://pl.porwoll.tech/api/posts?locale=de" +``` + +### Einzelnen Post abrufen + +```bash +# Nach ID +curl "https://pl.porwoll.tech/api/posts/1" + +# Nach Slug (über Query) +curl "https://pl.porwoll.tech/api/posts?where[slug][equals]=mein-erster-artikel" +``` + +### Post erstellen (Auth erforderlich) + +```bash +curl -X POST "https://pl.porwoll.tech/api/posts" \ + -H "Authorization: JWT your-token" \ + -H "Content-Type: application/json" \ + -d '{ + "tenant": 1, + "title": "Mein neuer Artikel", + "slug": "mein-neuer-artikel", + "type": "blog", + "isFeatured": false, + "excerpt": "Eine kurze Zusammenfassung des Artikels...", + "content": { + "root": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [{ "text": "Der Artikelinhalt..." }] + } + ] + } + }, + "status": "draft" + }' +``` + +### Post aktualisieren (Auth erforderlich) + +```bash +curl -X PATCH "https://pl.porwoll.tech/api/posts/1" \ + -H "Authorization: JWT your-token" \ + -H "Content-Type: application/json" \ + -d '{ + "status": "published", + "publishedAt": "2025-11-30T12:00:00.000Z" + }' +``` + +### Post löschen (Auth erforderlich) + +```bash +curl -X DELETE "https://pl.porwoll.tech/api/posts/1" \ + -H "Authorization: JWT your-token" +``` + +--- + +## Testimonials API + +### Alle Testimonials abrufen + +```bash +# Alle Testimonials +curl "https://pl.porwoll.tech/api/testimonials" + +# Nur aktive Testimonials +curl "https://pl.porwoll.tech/api/testimonials?where[isActive][equals]=true" + +# Sortiert nach Bewertung (beste zuerst) +curl "https://pl.porwoll.tech/api/testimonials?sort=-rating" + +# Sortiert nach eigener Reihenfolge +curl "https://pl.porwoll.tech/api/testimonials?sort=order" +``` + +### Testimonial erstellen (Auth erforderlich) + +```bash +curl -X POST "https://pl.porwoll.tech/api/testimonials" \ + -H "Authorization: JWT your-token" \ + -H "Content-Type: application/json" \ + -d '{ + "tenant": 1, + "quote": "Hervorragender Service! Ich bin sehr zufrieden.", + "author": "Max Mustermann", + "role": "Geschäftsführer", + "company": "Musterfirma GmbH", + "rating": 5, + "source": "Google Reviews", + "isActive": true, + "order": 1 + }' +``` + +--- + +## Newsletter Subscribers API + +### Newsletter-Anmeldung (Öffentlich) + +```bash +# Einfache Anmeldung +curl -X POST "https://pl.porwoll.tech/api/newsletter-subscribers" \ + -H "Content-Type: application/json" \ + -d '{ + "tenant": 1, + "email": "kunde@example.com", + "source": "website-footer" + }' + +# Mit Namen und Interessen +curl -X POST "https://pl.porwoll.tech/api/newsletter-subscribers" \ + -H "Content-Type: application/json" \ + -d '{ + "tenant": 1, + "email": "kunde@example.com", + "firstName": "Max", + "lastName": "Mustermann", + "interests": ["blog", "products"], + "source": "blog-sidebar" + }' +``` + +**Response (Erfolg):** +```json +{ + "doc": { + "id": 1, + "tenant": 1, + "email": "kunde@example.com", + "status": "pending", + "confirmationToken": "uuid-token-here", + "subscribedAt": "2025-11-30T14:23:41.012Z" + }, + "message": "Newsletter Subscriber successfully created." +} +``` + +### Subscribers abrufen (Auth erforderlich) + +```bash +# Alle Subscribers +curl "https://pl.porwoll.tech/api/newsletter-subscribers" \ + -H "Authorization: JWT your-token" + +# Nur bestätigte Subscribers +curl "https://pl.porwoll.tech/api/newsletter-subscribers?where[status][equals]=confirmed" \ + -H "Authorization: JWT your-token" + +# Nach E-Mail suchen +curl "https://pl.porwoll.tech/api/newsletter-subscribers?where[email][equals]=kunde@example.com" \ + -H "Authorization: JWT your-token" +``` + +### Subscriber bestätigen (Double Opt-In) + +```bash +# Über Token bestätigen +curl -X PATCH "https://pl.porwoll.tech/api/newsletter-subscribers/1" \ + -H "Authorization: JWT your-token" \ + -H "Content-Type: application/json" \ + -d '{ + "status": "confirmed" + }' +``` + +### Subscriber abmelden + +```bash +curl -X PATCH "https://pl.porwoll.tech/api/newsletter-subscribers/1" \ + -H "Authorization: JWT your-token" \ + -H "Content-Type: application/json" \ + -d '{ + "status": "unsubscribed" + }' +``` + +--- + +## Pages API + +### Seiten abrufen + +```bash +# Alle Seiten +curl "https://pl.porwoll.tech/api/pages" + +# Seite nach Slug +curl "https://pl.porwoll.tech/api/pages?where[slug][equals]=startseite" + +# Nur veröffentlichte Seiten +curl "https://pl.porwoll.tech/api/pages?where[status][equals]=published" + +# Mit Locale +curl "https://pl.porwoll.tech/api/pages?locale=de&depth=2" +``` + +### Seite mit Blocks + +Die Blocks werden im `layout`-Array zurückgegeben: + +```json +{ + "docs": [{ + "id": 1, + "title": "Startseite", + "slug": "startseite", + "layout": [ + { + "blockType": "hero-block", + "title": "Willkommen", + "subtitle": "..." + }, + { + "blockType": "posts-list-block", + "title": "Aktuelle News", + "postType": "news", + "limit": 3 + }, + { + "blockType": "testimonials-block", + "title": "Das sagen unsere Kunden", + "layout": "slider" + } + ] + }] +} +``` + +--- + +## Consent Management APIs + +### Cookie-Konfiguration abrufen (Öffentlich, Tenant-isoliert) + +Die Cookie-Konfiguration wird automatisch nach Domain gefiltert. + +```bash +# Für Frontend Cookie-Banner +curl "https://pl.porwoll.tech/api/cookie-configurations?where[tenant][equals]=1" +``` + +**Response:** +```json +{ + "docs": [{ + "id": 1, + "tenant": 1, + "title": "Cookie-Einstellungen", + "revision": 1, + "enabledCategories": ["necessary", "analytics"], + "translations": { + "de": { + "bannerTitle": "Wir respektieren Ihre Privatsphäre", + "bannerDescription": "Diese Website verwendet Cookies...", + "acceptAllButton": "Alle akzeptieren", + "acceptNecessaryButton": "Nur notwendige", + "settingsButton": "Einstellungen", + "saveButton": "Auswahl speichern", + "privacyPolicyUrl": "/datenschutz", + "categoryLabels": { + "necessary": { "title": "Notwendig", "description": "..." }, + "functional": { "title": "Funktional", "description": "..." }, + "analytics": { "title": "Statistik", "description": "..." }, + "marketing": { "title": "Marketing", "description": "..." } + } + } + }, + "styling": { + "position": "bottom", + "theme": "dark" + } + }] +} +``` + +### Cookie-Inventory abrufen (Öffentlich, Tenant-isoliert) + +Dokumentation aller verwendeten Cookies für die Datenschutzerklärung. + +```bash +# Alle Cookies eines Tenants +curl "https://pl.porwoll.tech/api/cookie-inventory?where[tenant][equals]=1" + +# Nur aktive Cookies +curl "https://pl.porwoll.tech/api/cookie-inventory?where[tenant][equals]=1&where[isActive][equals]=true" + +# Nach Kategorie filtern +curl "https://pl.porwoll.tech/api/cookie-inventory?where[tenant][equals]=1&where[category][equals]=analytics" +``` + +**Response:** +```json +{ + "docs": [{ + "id": 1, + "tenant": 1, + "name": "_ga", + "provider": "Google LLC", + "category": "analytics", + "duration": "2 Jahre", + "description": "Wird verwendet, um Benutzer zu unterscheiden.", + "isActive": true + }] +} +``` + +### Cookie-Inventory Felder + +| Feld | Typ | Beschreibung | +|------|-----|--------------| +| `name` | string | Technischer Cookie-Name (z.B. "_ga") | +| `provider` | string | Anbieter (z.B. "Google LLC") | +| `category` | enum | necessary, functional, analytics, marketing | +| `duration` | string | Speicherdauer (z.B. "2 Jahre") | +| `description` | string | Beschreibung für Endnutzer | +| `isActive` | boolean | Cookie aktiv? | + +### Consent-Log erstellen (API-Key erforderlich) + +Consent-Logs sind ein WORM (Write-Once-Read-Many) Audit-Trail für DSGVO-Nachweise. + +```bash +curl -X POST "https://pl.porwoll.tech/api/consent-logs" \ + -H "Content-Type: application/json" \ + -H "X-API-Key: your-consent-api-key" \ + -d '{ + "tenant": 1, + "clientRef": "uuid-from-cookie", + "categories": { + "necessary": true, + "functional": false, + "analytics": true, + "marketing": false + }, + "revision": 1, + "userAgent": "Mozilla/5.0..." + }' +``` + +**Response:** +```json +{ + "doc": { + "id": 1, + "consentId": "550e8400-e29b-41d4-a716-446655440000", + "tenant": 1, + "clientRef": "uuid-from-cookie", + "categories": { "necessary": true, "analytics": true, ... }, + "revision": 1, + "anonymizedIp": "a1b2c3d4e5f6...", + "expiresAt": "2028-12-02T00:00:00.000Z", + "createdAt": "2025-12-02T10:00:00.000Z" + } +} +``` + +**Wichtig:** +- CREATE: Nur mit gültigem `X-API-Key` Header +- READ: Nur authentifizierte Admin-User +- UPDATE: **Nicht erlaubt** (WORM-Prinzip) +- DELETE: **Nicht erlaubt** (nur via Retention-Job) + +### Consent-Log Felder + +| Feld | Typ | Beschreibung | +|------|-----|--------------| +| `consentId` | string | Server-generierte UUID (read-only) | +| `clientRef` | string | Client-Cookie-Referenz für Traceability | +| `tenant` | relation | Zugehöriger Tenant | +| `categories` | json | Akzeptierte Kategorien | +| `revision` | number | Konfigurationsversion zum Zeitpunkt der Zustimmung | +| `userAgent` | string | Browser-Information | +| `anonymizedIp` | string | HMAC-Hash der IP (täglich rotierend) | +| `expiresAt` | date | Automatische Löschung nach 3 Jahren | + +### Privacy Policy Settings abrufen (Öffentlich, Tenant-isoliert) + +Konfiguration für die Datenschutzerklärungs-Seite (z.B. Alfright Integration). + +```bash +curl "https://pl.porwoll.tech/api/privacy-policy-settings?where[tenant][equals]=1" +``` + +**Response:** +```json +{ + "docs": [{ + "id": 1, + "tenant": 1, + "title": "Datenschutzerklärung", + "provider": "alfright", + "alfright": { + "tenantId": "alfright_schutzteam", + "apiKey": "9f315103c43245bcb0806dd56c2be757", + "language": "de-de", + "iframeHeight": 4000 + }, + "styling": { + "headerColor": "#ca8a04", + "backgroundColor": "#111827", + ... + }, + "showCookieTable": true, + "cookieTableTitle": "Übersicht der verwendeten Cookies" + }] +} +``` + +--- + +## Query-Parameter + +### Filterung (where) + +```bash +# Equals +?where[field][equals]=value + +# Not equals +?where[field][not_equals]=value + +# Greater than +?where[field][greater_than]=10 + +# Less than +?where[field][less_than]=100 + +# Contains (Text) +?where[field][contains]=suchtext + +# In (Array) +?where[field][in]=value1,value2 + +# AND-Verknüpfung +?where[and][0][field1][equals]=value1&where[and][1][field2][equals]=value2 + +# OR-Verknüpfung +?where[or][0][field1][equals]=value1&where[or][1][field2][equals]=value2 +``` + +### Sortierung (sort) + +```bash +# Aufsteigend +?sort=fieldName + +# Absteigend +?sort=-fieldName + +# Mehrere Felder +?sort=-publishedAt,title +``` + +### Pagination + +```bash +# Limit +?limit=10 + +# Seite +?page=2 + +# Beide kombiniert +?limit=10&page=2 +``` + +### Depth (Relations laden) + +```bash +# Keine Relations laden +?depth=0 + +# Eine Ebene +?depth=1 + +# Zwei Ebenen +?depth=2 +``` + +### Locale (Mehrsprachigkeit) + +```bash +# Deutsch (Standard) +?locale=de + +# Englisch +?locale=en + +# Alle Sprachen +?locale=all +``` + +--- + +## Fehlerbehandlung + +### Häufige Fehlercodes + +| Code | Bedeutung | +|------|-----------| +| 200 | Erfolg | +| 201 | Erstellt | +| 400 | Ungültige Anfrage | +| 401 | Nicht authentifiziert | +| 403 | Nicht autorisiert | +| 404 | Nicht gefunden | +| 500 | Server-Fehler | + +### Fehler-Response + +```json +{ + "errors": [ + { + "message": "Du hast keine Berechtigung, diese Aktion auszuführen." + } + ] +} +``` + +### Validierungsfehler + +```json +{ + "errors": [ + { + "message": "The following field is invalid: email", + "field": "email" + } + ] +} +``` + +--- + +## Multi-Tenant Hinweise + +### Tenant-ID ermitteln + +1. **Admin Panel:** Unter "Settings → Tenants" die ID ablesen +2. **API:** +```bash +curl "https://pl.porwoll.tech/api/tenants" \ + -H "Authorization: JWT your-token" +``` + +### Domain-basierter Zugriff + +Wenn ein Frontend über eine Tenant-Domain (z.B. `porwoll.de`) zugreift, wird der Tenant automatisch erkannt und nur dessen Daten zurückgegeben. + +### Manueller Tenant-Filter + +Für direkte API-Zugriffe: +```bash +curl "https://pl.porwoll.tech/api/posts?where[tenant][equals]=1" +``` + +### Super Admin + +User mit `isSuperAdmin: true` haben Zugriff auf alle Tenants und können neue Tenants erstellen/bearbeiten. + +--- + +## Beispiel: Frontend-Integration + +### Next.js Beispiel + +```typescript +// lib/api.ts +const API_BASE = 'https://pl.porwoll.tech/api' +const TENANT_ID = 1 + +export async function getPosts(type?: string, limit = 10, locale = 'de') { + const params = new URLSearchParams({ + 'where[tenant][equals]': String(TENANT_ID), + 'where[status][equals]': 'published', + limit: String(limit), + sort: '-publishedAt', + depth: '1', + locale, + }) + + if (type && type !== 'all') { + params.append('where[type][equals]', type) + } + + const res = await fetch(`${API_BASE}/posts?${params}`) + return res.json() +} + +export async function getTestimonials(limit = 6) { + const params = new URLSearchParams({ + 'where[tenant][equals]': String(TENANT_ID), + 'where[isActive][equals]': 'true', + limit: String(limit), + sort: 'order', + depth: '1', + }) + + const res = await fetch(`${API_BASE}/testimonials?${params}`) + return res.json() +} + +export async function getCookieConfig() { + const params = new URLSearchParams({ + 'where[tenant][equals]': String(TENANT_ID), + }) + + const res = await fetch(`${API_BASE}/cookie-configurations?${params}`) + const data = await res.json() + return data.docs[0] || null +} + +export async function getCookieInventory() { + const params = new URLSearchParams({ + 'where[tenant][equals]': String(TENANT_ID), + 'where[isActive][equals]': 'true', + sort: 'category', + }) + + const res = await fetch(`${API_BASE}/cookie-inventory?${params}`) + return res.json() +} + +export async function logConsent( + categories: Record, + revision: number, + clientRef: string, + apiKey: string +) { + const res = await fetch(`${API_BASE}/consent-logs`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': apiKey, + }, + body: JSON.stringify({ + tenant: TENANT_ID, + clientRef, + categories, + revision, + userAgent: navigator.userAgent, + }), + }) + return res.json() +} + +export async function subscribeNewsletter(email: string, source: string) { + const res = await fetch(`${API_BASE}/newsletter-subscribers`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + tenant: TENANT_ID, + email, + source, + }), + }) + return res.json() +} +``` + +### React Component + +```tsx +// components/NewsletterForm.tsx +import { useState } from 'react' +import { subscribeNewsletter } from '@/lib/api' + +export function NewsletterForm() { + const [email, setEmail] = useState('') + const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle') + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setStatus('loading') + + try { + const result = await subscribeNewsletter(email, 'website-footer') + if (result.doc) { + setStatus('success') + setEmail('') + } else { + setStatus('error') + } + } catch { + setStatus('error') + } + } + + return ( +
+ setEmail(e.target.value)} + placeholder="Ihre E-Mail-Adresse" + required + /> + + {status === 'success' &&

Vielen Dank! Bitte bestätigen Sie Ihre E-Mail.

} + {status === 'error' &&

Es ist ein Fehler aufgetreten.

} +
+ ) +} +``` + +--- + +## Rate Limiting + +Das System verwendet einen zentralen Rate-Limiter mit Redis-Backend (Fallback auf In-Memory). + +### Vordefinierte Limits + +| Limiter | Limit | Fenster | Verwendung | +|---------|-------|---------|------------| +| `publicApiLimiter` | 60 Requests | 1 Minute | Öffentliche API-Endpunkte | +| `authLimiter` | 5 Requests | 15 Minuten | Login-Versuche | +| `emailLimiter` | 10 Requests | 1 Minute | E-Mail-Versand | +| `searchLimiter` | 30 Requests | 1 Minute | Suche & Posts-API | +| `formLimiter` | 5 Requests | 10 Minuten | Formular-Submissions, Newsletter | + +### Response bei Rate-Limit + +```json +{ + "error": "Too many requests", + "retryAfter": 60 +} +``` + +**HTTP Status:** 429 Too Many Requests + +**Headers:** +- `X-RateLimit-Limit`: Maximale Requests +- `X-RateLimit-Remaining`: Verbleibende Requests +- `X-RateLimit-Reset`: Reset-Zeitpunkt (Unix Timestamp) +- `Retry-After`: Sekunden bis zum Reset + +--- + +## Alle Collections Übersicht + +### Core Collections + +| Collection | Slug | Öffentlich | Beschreibung | +|------------|------|------------|--------------| +| Users | `users` | Nein | Benutzer-Verwaltung | +| Tenants | `tenants` | Nein | Mandanten | +| Media | `media` | Ja | Bilder und Dateien (11 responsive Sizes) | +| Pages | `pages` | Ja | Seiten mit Blocks | + +### Content Collections + +| Collection | Slug | Öffentlich | Beschreibung | +|------------|------|------------|--------------| +| Posts | `posts` | Ja | Blog, News, Presse, Ankündigungen | +| Categories | `categories` | Ja | Kategorien für Posts | +| Tags | `tags` | Ja | Tags für Posts | +| Authors | `authors` | Ja | Autoren für Posts | +| Testimonials | `testimonials` | Ja | Kundenbewertungen | +| FAQs | `faqs` | Ja | Häufig gestellte Fragen | +| Social Links | `social-links` | Ja | Social Media Links | + +### Team & Services + +| Collection | Slug | Öffentlich | Beschreibung | +|------------|------|------------|--------------| +| Team | `team` | Ja | Team-Mitglieder | +| Service Categories | `service-categories` | Ja | Kategorien für Leistungen | +| Services | `services` | Ja | Leistungen/Dienstleistungen | +| Jobs | `jobs` | Ja | Stellenangebote | + +### Portfolio & Media + +| Collection | Slug | Öffentlich | Beschreibung | +|------------|------|------------|--------------| +| Portfolios | `portfolios` | Ja | Portfolio-Galerien | +| Portfolio Categories | `portfolio-categories` | Ja | Kategorien für Portfolios | +| Videos | `videos` | Ja | Video-Bibliothek (YouTube/Vimeo/Upload) | +| Video Categories | `video-categories` | Ja | Kategorien für Videos | + +### Products (E-Commerce) + +| Collection | Slug | Öffentlich | Beschreibung | +|------------|------|------------|--------------| +| Products | `products` | Ja | Produkte | +| Product Categories | `product-categories` | Ja | Produkt-Kategorien | + +### Feature Collections + +| Collection | Slug | Öffentlich | Beschreibung | +|------------|------|------------|--------------| +| Locations | `locations` | Ja | Standorte/Filialen | +| Partners | `partners` | Ja | Partner/Kunden (Logos) | +| Downloads | `downloads` | Ja | Download-Dateien | +| Events | `events` | Ja | Veranstaltungen | +| Timelines | `timelines` | Ja | Chronologische Events | +| Workflows | `workflows` | Ja | Prozess-Darstellungen | + +### BlogWoman Collections + +| Collection | Slug | Öffentlich | Beschreibung | +|------------|------|------------|--------------| +| Favorites | `favorites` | Ja | Affiliate-Produkte mit Kategorien/Badges | +| Series | `series` | Ja | YouTube-Serien mit Branding | + +### Community Management (YouTube/Meta) + +| Collection | Slug | Öffentlich | Beschreibung | +|------------|------|------------|--------------| +| YouTube Channels | `youtube-channels` | Nein | Multi-Kanal-Verwaltung | +| YouTube Content | `youtube-content` | Nein | Videos + Shorts mit Statistiken | +| YT Series | `yt-series` | Nein | Serien mit Branding (Logo, Farben) | +| YT Notifications | `yt-notifications` | Nein | Handlungsbedarf-System | +| Social Platforms | `social-platforms` | Nein | Plattform-Konfiguration | +| Social Accounts | `social-accounts` | Nein | OAuth-Verbindungen | +| Community Interactions | `community-interactions` | Nein | Kommentare/Nachrichten aller Plattformen | + +### Formulare & Newsletter + +| Collection | Slug | Öffentlich | Beschreibung | +|------------|------|------------|--------------| +| Forms | `forms` | Nein | Formular-Builder (Plugin) | +| Form Submissions | `form-submissions` | Nein | Formular-Einsendungen mit CRM-Workflow | +| Newsletter Subscribers | `newsletter-subscribers` | Create: Ja | Newsletter mit Double Opt-In | + +### Consent & Privacy (DSGVO) + +| Collection | Slug | Öffentlich | Beschreibung | +|------------|------|------------|--------------| +| Cookie Configurations | `cookie-configurations` | Ja (Tenant-isoliert) | Cookie-Banner Konfiguration | +| Cookie Inventory | `cookie-inventory` | Ja (Tenant-isoliert) | Cookie-Dokumentation | +| Consent Logs | `consent-logs` | Nein (API-Key) | WORM Audit-Trail | +| Privacy Policy Settings | `privacy-policy-settings` | Ja (Tenant-isoliert) | Datenschutz-Konfiguration | + +### Tenant-spezifische Collections + +| Collection | Slug | Tenant | Beschreibung | +|------------|------|--------|--------------| +| Bookings | `bookings` | porwoll.de | Fotografie-Buchungen | +| Certifications | `certifications` | C2S | Zertifizierungen | +| Projects | `projects` | gunshin.de | Game-Development-Projekte | + +### System Collections + +| Collection | Slug | Öffentlich | Beschreibung | +|------------|------|------------|--------------| +| Site Settings | `site-settings` | Ja (Tenant-isoliert) | Website-Einstellungen | +| Navigations | `navigations` | Ja (Tenant-isoliert) | Navigationsmenüs | +| Email Logs | `email-logs` | Nein | E-Mail-Protokollierung | +| Audit Logs | `audit-logs` | Nein | Security Audit Trail | +| Redirects | `redirects` | Nein | URL-Weiterleitungen (Plugin) | + +--- + +## BlogWoman APIs (NEU) + +### Favorites API (Affiliate-Produkte) + +```bash +# Alle Favorites eines Tenants +curl "https://pl.porwoll.tech/api/favorites?where[tenant][equals]=1" + +# Nach Kategorie filtern +curl "https://pl.porwoll.tech/api/favorites?where[tenant][equals]=1&where[category][equals]=fashion" + +# Nach Badge filtern +curl "https://pl.porwoll.tech/api/favorites?where[tenant][equals]=1&where[badge][equals]=bestseller" + +# Nach Preisbereich filtern +curl "https://pl.porwoll.tech/api/favorites?where[tenant][equals]=1&where[priceRange][equals]=mid" +``` + +**Felder:** + +| Feld | Typ | Beschreibung | +|------|-----|--------------| +| `title` | string | Produktname | +| `slug` | string | URL-Pfad | +| `description` | richtext | Beschreibung | +| `image` | relation | Produktbild | +| `affiliateUrl` | string | Affiliate-Link | +| `price` | string | Preis (Freitext) | +| `category` | select | fashion, beauty, travel, tech, home | +| `badge` | select | investment-piece, daily-driver, grfi-approved, new, bestseller | +| `priceRange` | select | budget, mid, premium, luxury | +| `isActive` | boolean | Sichtbarkeit | + +### Series API (YouTube-Serien) + +```bash +# Alle Serien eines Tenants +curl "https://pl.porwoll.tech/api/series?where[tenant][equals]=1" + +# Nur aktive Serien +curl "https://pl.porwoll.tech/api/series?where[tenant][equals]=1&where[isActive][equals]=true" + +# Einzelne Serie nach Slug +curl "https://pl.porwoll.tech/api/series?where[tenant][equals]=1&where[slug][equals]=grfi" +``` + +**Felder:** + +| Feld | Typ | Beschreibung | +|------|-----|--------------| +| `title` | string | Serienname (lokalisiert) | +| `slug` | string | URL-Pfad | +| `description` | richtext | Beschreibung (lokalisiert) | +| `logo` | relation | Serien-Logo | +| `coverImage` | relation | Cover-Bild | +| `brandColor` | string | Hex-Farbcode | +| `youtubePlaylistId` | string | YouTube Playlist ID | +| `isActive` | boolean | Sichtbarkeit | + +--- + +## Community Management APIs (YouTube/Meta) + +### YouTube Channels API + +```bash +# Alle Kanäle +curl "https://pl.porwoll.tech/api/youtube-channels" \ + -H "Authorization: JWT your-token" + +# Einzelner Kanal +curl "https://pl.porwoll.tech/api/youtube-channels/1" \ + -H "Authorization: JWT your-token" +``` + +**Felder:** + +| Feld | Typ | Beschreibung | +|------|-----|--------------| +| `channelId` | string | YouTube Channel ID | +| `title` | string | Kanalname | +| `handle` | string | @handle | +| `thumbnailUrl` | string | Profilbild-URL | +| `subscriberCount` | number | Abonnenten | +| `videoCount` | number | Anzahl Videos | +| `isActive` | boolean | Sync aktiv | + +### YouTube Content API + +```bash +# Alle Videos eines Kanals +curl "https://pl.porwoll.tech/api/youtube-content?where[channel][equals]=1" \ + -H "Authorization: JWT your-token" + +# Nur Videos (keine Shorts) +curl "https://pl.porwoll.tech/api/youtube-content?where[contentType][equals]=video" \ + -H "Authorization: JWT your-token" + +# Videos einer Serie +curl "https://pl.porwoll.tech/api/youtube-content?where[series][equals]=1" \ + -H "Authorization: JWT your-token" + +# Nach Veröffentlichungsdatum sortiert +curl "https://pl.porwoll.tech/api/youtube-content?sort=-publishedAt&limit=10" \ + -H "Authorization: JWT your-token" +``` + +**Felder:** + +| Feld | Typ | Beschreibung | +|------|-----|--------------| +| `videoId` | string | YouTube Video ID | +| `title` | string | Video-Titel | +| `description` | text | Beschreibung | +| `thumbnailUrl` | string | Thumbnail-URL | +| `publishedAt` | date | Veröffentlichungsdatum | +| `contentType` | select | video, short | +| `channel` | relation | YouTube-Kanal | +| `series` | relation | YT-Serie | +| `viewCount` | number | Aufrufe | +| `likeCount` | number | Likes | +| `commentCount` | number | Kommentare | + +### YT Series API + +```bash +# Alle Serien eines Kanals +curl "https://pl.porwoll.tech/api/yt-series?where[channel][equals]=1" \ + -H "Authorization: JWT your-token" + +# Nur aktive Serien +curl "https://pl.porwoll.tech/api/yt-series?where[isActive][equals]=true" \ + -H "Authorization: JWT your-token" +``` + +**Felder:** + +| Feld | Typ | Beschreibung | +|------|-----|--------------| +| `name` | string | Serienname (lokalisiert) | +| `slug` | string | URL-Pfad | +| `description` | textarea | Beschreibung (lokalisiert) | +| `channel` | relation | YouTube-Kanal | +| `logo` | relation | Serien-Logo | +| `coverImage` | relation | Cover-Bild | +| `brandColor` | string | Primärfarbe (Hex) | +| `accentColor` | string | Akzentfarbe (Hex) | +| `youtubePlaylistId` | string | Playlist ID | +| `format` | select | short, longform, mixed | +| `isActive` | boolean | Sichtbarkeit | + +### YT Notifications API (Auth erforderlich) + +```bash +# Alle Benachrichtigungen +curl "https://pl.porwoll.tech/api/yt-notifications" \ + -H "Authorization: JWT your-token" + +# Nur ungelesene +curl "https://pl.porwoll.tech/api/yt-notifications?where[status][equals]=unread" \ + -H "Authorization: JWT your-token" + +# Nach Priorität +curl "https://pl.porwoll.tech/api/yt-notifications?where[priority][equals]=high" \ + -H "Authorization: JWT your-token" +``` + +**Felder:** + +| Feld | Typ | Beschreibung | +|------|-----|--------------| +| `type` | select | comment, mention, milestone, alert | +| `priority` | select | low, medium, high, urgent | +| `status` | select | unread, read, actioned, dismissed | +| `title` | string | Benachrichtigungstitel | +| `message` | text | Details | +| `channel` | relation | Betroffener Kanal | +| `content` | relation | Betroffenes Video | + +### Community Interactions API (Auth erforderlich) + +```bash +# Alle Interaktionen +curl "https://pl.porwoll.tech/api/community-interactions" \ + -H "Authorization: JWT your-token" + +# Nach Plattform filtern +curl "https://pl.porwoll.tech/api/community-interactions?where[platform][equals]=1" \ + -H "Authorization: JWT your-token" + +# Nur Kommentare +curl "https://pl.porwoll.tech/api/community-interactions?where[type][equals]=comment" \ + -H "Authorization: JWT your-token" + +# Nach Status +curl "https://pl.porwoll.tech/api/community-interactions?where[status][equals]=pending" \ + -H "Authorization: JWT your-token" + +# Nach Sentiment (AI-Analyse) +curl "https://pl.porwoll.tech/api/community-interactions?where[analysis.sentiment][equals]=negative" \ + -H "Authorization: JWT your-token" +``` + +**Felder:** + +| Feld | Typ | Beschreibung | +|------|-----|--------------| +| `platform` | relation | Social Platform | +| `socialAccount` | relation | Social Account | +| `type` | select | comment, reply, dm, mention | +| `externalId` | string | Plattform-ID | +| `author.name` | string | Autor-Name | +| `author.handle` | string | Autor-Handle | +| `message` | text | Nachrichteninhalt | +| `publishedAt` | date | Veröffentlichungsdatum | +| `status` | select | pending, read, in-progress, done, archived | +| `priority` | select | low, normal, high | +| `analysis.sentiment` | select | positive, neutral, negative | +| `analysis.language` | string | Erkannte Sprache | + +### Social Platforms API + +```bash +# Alle Plattformen +curl "https://pl.porwoll.tech/api/social-platforms" \ + -H "Authorization: JWT your-token" +``` + +**Felder:** + +| Feld | Typ | Beschreibung | +|------|-----|--------------| +| `name` | string | Plattformname | +| `slug` | string | youtube, facebook, instagram | +| `icon` | string | Emoji | +| `color` | string | Brand Color (Hex) | +| `apiConfig.apiType` | select | youtube_v3, facebook_graph, instagram_graph | +| `apiConfig.authType` | select | oauth2, api_key, bearer | +| `isActive` | boolean | Plattform aktiv | + +### Social Accounts API (Auth erforderlich) + +```bash +# Alle verbundenen Accounts +curl "https://pl.porwoll.tech/api/social-accounts" \ + -H "Authorization: JWT your-token" + +# Nach Plattform filtern +curl "https://pl.porwoll.tech/api/social-accounts?where[platform][equals]=1" \ + -H "Authorization: JWT your-token" +``` + +**Felder:** + +| Feld | Typ | Beschreibung | +|------|-----|--------------| +| `platform` | relation | Social Platform | +| `displayName` | string | Anzeigename | +| `accountHandle` | string | @handle | +| `externalId` | string | Plattform-Account-ID | +| `credentials.accessToken` | string | OAuth Token (verschlüsselt) | +| `credentials.tokenExpiresAt` | date | Token-Ablauf | +| `stats.lastSyncedAt` | date | Letzter Sync | +| `isActive` | boolean | Sync aktiv | + +--- + +## Weitere Ressourcen + +- **Admin Panel:** https://pl.porwoll.tech/admin +- **API-Dokumentation (Swagger):** https://pl.porwoll.tech/api/docs +- **OpenAPI JSON:** https://pl.porwoll.tech/api/openapi.json +- **Payload CMS Docs:** https://payloadcms.com/docs +- **GraphQL Playground:** https://pl.porwoll.tech/api/graphql (wenn aktiviert) + +--- + +*Letzte Aktualisierung: 17.01.2026* diff --git a/docs/architecture/Analytics.md b/docs/architecture/Analytics.md new file mode 100644 index 0000000..fc6b9aa --- /dev/null +++ b/docs/architecture/Analytics.md @@ -0,0 +1,981 @@ +# ANALYTICS-LÖSUNG: Implementierungsübersicht für Payload CMS + +*Letzte Aktualisierung: 18. Dezember 2025* + +## Kontext + +Du entwickelst das Multi-Tenant Payload CMS Backend und Next.js Frontend für 4 Websites. Diese Dokumentation beschreibt die Analytics-Lösung, die in das Frontend integriert werden muss. + +**Wichtig:** Frontends verwenden die **Production-API** (cms.c2sgmbh.de) und **Production-Analytics** (analytics.c2sgmbh.de). + +--- + +## Architektur-Übersicht + +``` +┌─────────────────────────────────────────────────────────────────────────────────────┐ +│ ANALYTICS ARCHITEKTUR │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────────┐ │ +│ │ OHNE CONSENT (immer aktiv) │ │ +│ │ │ │ +│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ +│ │ │ UMAMI ANALYTICS │ │ │ +│ │ │ │ │ │ +│ │ │ Server: sv-analytics (10.10.181.103:3000) │ │ │ +│ │ │ Dashboard: http://10.10.181.103:3000 │ │ │ +│ │ │ Script: /custom.js (Anti-Adblock) │ │ │ +│ │ │ Endpoint: /api/send │ │ │ +│ │ │ │ │ │ +│ │ │ Features: │ │ │ +│ │ │ • Cookieless Tracking (DSGVO-konform ohne Einwilligung) │ │ │ +│ │ │ • Pageviews, Sessions, Referrer, UTM-Parameter │ │ │ +│ │ │ • Custom Events (Newsletter, Formulare, CTAs, Downloads) │ │ │ +│ │ │ • 100% Erfassung aller Besucher │ │ │ +│ │ └─────────────────────────────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────────┐ │ +│ │ MIT CONSENT (Kategorie: "marketing") │ │ +│ │ │ │ +│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ +│ │ │ GOOGLE ADS CONVERSION │ │ │ +│ │ │ │ │ │ +│ │ │ Client-Side (bei Consent): │ │ │ +│ │ │ • Google Ads Tag (gtag.js) │ │ │ +│ │ │ • Conversion Tracking │ │ │ +│ │ │ • Remarketing Audiences │ │ │ +│ │ │ │ │ │ +│ │ │ Server-Side (immer, anonymisiert): │ │ │ +│ │ │ • Google Ads Conversion API │ │ │ +│ │ │ • Enhanced Conversions (gehashte E-Mail) │ │ │ +│ │ │ • GCLID-basierte Attribution │ │ │ +│ │ └─────────────────────────────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ +│ │ │ GOOGLE CONSENT MODE v2 │ │ │ +│ │ │ │ │ │ +│ │ │ Integration mit bestehendem Orestbida Consent-Banner │ │ │ +│ │ │ Kategorie "marketing" steuert: │ │ │ +│ │ │ • ad_storage │ │ │ +│ │ │ • ad_user_data │ │ │ +│ │ │ • ad_personalization │ │ │ +│ │ └─────────────────────────────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Infrastruktur + +### Umami Server + +| Umgebung | Server | URL | Dashboard | +|----------|--------|-----|-----------| +| **Production** | Hetzner 3 | https://analytics.c2sgmbh.de | https://analytics.c2sgmbh.de | +| **Development** | sv-analytics (LXC 703) | https://umami.porwoll.tech | https://umami.porwoll.tech | + +| Eigenschaft | Production | Development | +|-------------|------------|-------------| +| **Tracking Script** | https://analytics.c2sgmbh.de/script.js | https://umami.porwoll.tech/script.js | +| **API Endpoint** | https://analytics.c2sgmbh.de/api/send | https://umami.porwoll.tech/api/send | +| **Datenbank** | umami_db auf Hetzner 3 | umami_db auf sv-postgres | + +### Website-IDs (Multi-Tenant) + +Die Website-IDs werden in Umami für jeden Tenant erstellt: + +```typescript +// src/config/analytics.ts + +export const UMAMI_WEBSITE_IDS: Record = { + 'porwoll.de': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + 'complexcaresolutions.de': 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy', + 'gunshin.de': 'zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz', + 'zweitmeinu.ng': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', +} + +// Production URL für Frontends +export const UMAMI_HOST = process.env.NEXT_PUBLIC_UMAMI_HOST || 'https://analytics.c2sgmbh.de' +``` + +--- + +## Frontend-Integration + +### 1. Umami Script Komponente + +```typescript +// src/components/analytics/UmamiScript.tsx + +'use client' + +import Script from 'next/script' + +interface UmamiScriptProps { + websiteId: string + host?: string +} + +export function UmamiScript({ + websiteId, + host = 'https://analytics.c2sgmbh.de' // Production default +}: UmamiScriptProps) { + if (!websiteId) return null + + return ( +