cms.c2sgmbh/docs/anleitungen/API_ANLEITUNG.md
Martin Porwoll a88e4f60d0 test: add E2E and integration tests with documentation
Tests:
- Update frontend.e2e.spec.ts with locale testing
- Add search.e2e.spec.ts for search functionality
- Add i18n.int.spec.ts for localization tests
- Add search.int.spec.ts for search integration
- Update playwright.config.ts

Documentation:
- Add CLAUDE.md with project instructions
- Add docs/ directory with detailed documentation
- Add scripts/ for utility scripts

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 08:19:52 +00:00

11 KiB

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 die Universal Features.

Base URL: https://pl.c2sgmbh.de/api


Authentifizierung

Login

curl -X POST "https://pl.c2sgmbh.de/api/users/login" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@example.com",
    "password": "your-password"
  }'

Response:

{
  "message": "Auth Passed",
  "user": { ... },
  "token": "eyJhbGciOiJIUzI1NiIs..."
}

Token verwenden

curl "https://pl.c2sgmbh.de/api/posts" \
  -H "Authorization: JWT eyJhbGciOiJIUzI1NiIs..."

Posts API

Alle Posts abrufen

# Alle Posts
curl "https://pl.c2sgmbh.de/api/posts"

# Nur Blog-Artikel
curl "https://pl.c2sgmbh.de/api/posts?where[type][equals]=blog"

# Nur News
curl "https://pl.c2sgmbh.de/api/posts?where[type][equals]=news"

# Nur veröffentlichte Posts
curl "https://pl.c2sgmbh.de/api/posts?where[status][equals]=published"

# Nur hervorgehobene Posts
curl "https://pl.c2sgmbh.de/api/posts?where[isFeatured][equals]=true"

# Mit Sortierung (neueste zuerst)
curl "https://pl.c2sgmbh.de/api/posts?sort=-publishedAt"

# Limitiert auf 10 Einträge
curl "https://pl.c2sgmbh.de/api/posts?limit=10"

# Pagination (Seite 2)
curl "https://pl.c2sgmbh.de/api/posts?limit=10&page=2"

Einzelnen Post abrufen

# Nach ID
curl "https://pl.c2sgmbh.de/api/posts/1"

# Nach Slug (über Query)
curl "https://pl.c2sgmbh.de/api/posts?where[slug][equals]=mein-erster-artikel"

Post erstellen (Auth erforderlich)

curl -X POST "https://pl.c2sgmbh.de/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)

curl -X PATCH "https://pl.c2sgmbh.de/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)

curl -X DELETE "https://pl.c2sgmbh.de/api/posts/1" \
  -H "Authorization: JWT your-token"

Testimonials API

Alle Testimonials abrufen

# Alle Testimonials
curl "https://pl.c2sgmbh.de/api/testimonials"

# Nur aktive Testimonials
curl "https://pl.c2sgmbh.de/api/testimonials?where[isActive][equals]=true"

# Sortiert nach Bewertung (beste zuerst)
curl "https://pl.c2sgmbh.de/api/testimonials?sort=-rating"

# Sortiert nach eigener Reihenfolge
curl "https://pl.c2sgmbh.de/api/testimonials?sort=order"

Testimonial erstellen (Auth erforderlich)

curl -X POST "https://pl.c2sgmbh.de/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)

# Einfache Anmeldung
curl -X POST "https://pl.c2sgmbh.de/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.c2sgmbh.de/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):

{
  "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)

# Alle Subscribers
curl "https://pl.c2sgmbh.de/api/newsletter-subscribers" \
  -H "Authorization: JWT your-token"

# Nur bestätigte Subscribers
curl "https://pl.c2sgmbh.de/api/newsletter-subscribers?where[status][equals]=confirmed" \
  -H "Authorization: JWT your-token"

# Nach E-Mail suchen
curl "https://pl.c2sgmbh.de/api/newsletter-subscribers?where[email][equals]=kunde@example.com" \
  -H "Authorization: JWT your-token"

Subscriber bestätigen (Double Opt-In)

# Über Token bestätigen
curl -X PATCH "https://pl.c2sgmbh.de/api/newsletter-subscribers/1" \
  -H "Authorization: JWT your-token" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "confirmed"
  }'

Subscriber abmelden

curl -X PATCH "https://pl.c2sgmbh.de/api/newsletter-subscribers/1" \
  -H "Authorization: JWT your-token" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "unsubscribed"
  }'

Pages API

Seiten abrufen

# Alle Seiten
curl "https://pl.c2sgmbh.de/api/pages"

# Seite nach Slug
curl "https://pl.c2sgmbh.de/api/pages?where[slug][equals]=startseite"

# Nur veröffentlichte Seiten
curl "https://pl.c2sgmbh.de/api/pages?where[status][equals]=published"

Seite mit Blocks

Die Blocks werden im layout-Array zurückgegeben:

{
  "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"
      }
    ]
  }]
}

Query-Parameter

Filterung (where)

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

# Aufsteigend
?sort=fieldName

# Absteigend
?sort=-fieldName

# Mehrere Felder
?sort=-publishedAt,title

Pagination

# Limit
?limit=10

# Seite
?page=2

# Beide kombiniert
?limit=10&page=2

Depth (Relations laden)

# Keine Relations laden
?depth=0

# Eine Ebene
?depth=1

# Zwei Ebenen
?depth=2

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

{
  "errors": [
    {
      "message": "You are not allowed to perform this action."
    }
  ]
}

Validierungsfehler

{
  "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:
curl "https://pl.c2sgmbh.de/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:

curl "https://pl.c2sgmbh.de/api/posts?where[tenant][equals]=1"

Beispiel: Frontend-Integration

Next.js Beispiel

// lib/api.ts
const API_BASE = 'https://pl.c2sgmbh.de/api'
const TENANT_ID = 1

export async function getPosts(type?: string, limit = 10) {
  const params = new URLSearchParams({
    'where[tenant][equals]': String(TENANT_ID),
    'where[status][equals]': 'published',
    limit: String(limit),
    sort: '-publishedAt',
    depth: '1',
  })

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

// 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 (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Ihre E-Mail-Adresse"
        required
      />
      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Wird gesendet...' : 'Anmelden'}
      </button>
      {status === 'success' && <p>Vielen Dank! Bitte bestätigen Sie Ihre E-Mail.</p>}
      {status === 'error' && <p>Es ist ein Fehler aufgetreten.</p>}
    </form>
  )
}

Rate Limiting

Aktuell gibt es kein Rate Limiting. Für Production-Umgebungen sollte ein Reverse Proxy (z.B. Caddy, nginx) mit Rate Limiting konfiguriert werden.


Weitere Ressourcen