cms.c2sgmbh/docs/PROMPT_PAYLOAD_API_CONFIG.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

8.6 KiB

Payload API Konfiguration für externe Frontend-Zugriffe

Kontext

Du arbeitest im Verzeichnis /home/payload/payload-cms auf dem Server sv-payload (10.10.181.100).

Das Frontend wird auf einem separaten Development-Server entwickelt:

  • IP: 10.10.180.153
  • Domain: dev.zh3.de
  • Projekt: frontend-porwoll

Payload CMS muss so konfiguriert werden, dass externe Frontends auf die API zugreifen können.

Aufgabe

  1. CORS konfigurieren für Frontend-Zugriff
  2. API-Zugriff ohne Authentifizierung für öffentliche Inhalte ermöglichen
  3. Optional: GraphQL aktivieren
  4. API Key für geschützte Operationen erstellen

Schritt 1: CORS Konfiguration

Aktualisiere src/payload.config.ts:

import { buildConfig } from 'payload'
import { postgresAdapter } from '@payloadcms/db-postgres'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
import { multiTenantPlugin } from '@payloadcms/plugin-multi-tenant'
import path from 'path'
import { fileURLToPath } from 'url'

// Collections
import { Users } from './collections/Users'
import { Media } from './collections/Media'
import { Tenants } from './collections/Tenants'
import { Pages } from './collections/Pages'
import { Posts } from './collections/Posts'
import { Categories } from './collections/Categories'
import { SocialLinks } from './collections/SocialLinks'

// Globals
import { SiteSettings } from './globals/SiteSettings'
import { Navigation } from './globals/Navigation'

const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)

export default buildConfig({
  admin: {
    user: Users.slug,
  },
  
  // CORS Konfiguration für externe Frontends
  cors: [
    'http://localhost:3000',
    'http://localhost:3001',
    'http://10.10.180.153:3000',
    'http://10.10.180.153:3001',
    'https://dev.zh3.de',
    'https://porwoll.de',
    'https://www.porwoll.de',
  ],
  
  // CSRF Protection - gleiche Origins
  csrf: [
    'http://localhost:3000',
    'http://localhost:3001',
    'http://10.10.180.153:3000',
    'http://10.10.180.153:3001',
    'https://dev.zh3.de',
    'https://porwoll.de',
    'https://www.porwoll.de',
  ],

  collections: [Users, Media, Tenants, Pages, Posts, Categories, SocialLinks],
  
  globals: [SiteSettings, Navigation],
  
  editor: lexicalEditor(),
  
  secret: process.env.PAYLOAD_SECRET || '',
  
  typescript: {
    outputFile: path.resolve(dirname, 'payload-types.ts'),
  },
  
  db: postgresAdapter({
    pool: {
      connectionString: process.env.DATABASE_URI || '',
    },
  }),
  
  plugins: [
    multiTenantPlugin({
      tenantsSlug: 'tenants',
      collections: {
        media: {},
        pages: {},
        posts: {},
        categories: {},
        'social-links': {},
      },
      debug: true,
    }),
  ],
})

Schritt 2: Öffentlichen API-Zugriff konfigurieren

Für öffentliche Inhalte (Pages, Posts) muss der read-Zugriff ohne Auth erlaubt werden.

Pages Collection aktualisieren (src/collections/Pages.ts)

Füge Access Control hinzu:

import type { CollectionConfig } from 'payload'

export const Pages: CollectionConfig = {
  slug: 'pages',
  admin: {
    useAsTitle: 'title',
    defaultColumns: ['title', 'slug', 'status', 'updatedAt'],
  },
  // Öffentlicher Lesezugriff für veröffentlichte Seiten
  access: {
    read: ({ req }) => {
      // Eingeloggte User sehen alles
      if (req.user) return true
      // Öffentlich: nur veröffentlichte Seiten
      return {
        status: {
          equals: 'published',
        },
      }
    },
    create: ({ req }) => !!req.user,
    update: ({ req }) => !!req.user,
    delete: ({ req }) => !!req.user,
  },
  fields: [
    // ... bestehende Felder
  ],
}

Posts Collection aktualisieren (src/collections/Posts.ts)

import type { CollectionConfig } from 'payload'

export const Posts: CollectionConfig = {
  slug: 'posts',
  admin: {
    useAsTitle: 'title',
    defaultColumns: ['title', 'category', 'status', 'publishedAt'],
  },
  access: {
    read: ({ req }) => {
      if (req.user) return true
      return {
        status: {
          equals: 'published',
        },
      }
    },
    create: ({ req }) => !!req.user,
    update: ({ req }) => !!req.user,
    delete: ({ req }) => !!req.user,
  },
  fields: [
    // ... bestehende Felder
  ],
}

Media Collection aktualisieren (src/collections/Media.ts)

import type { CollectionConfig } from 'payload'

export const Media: CollectionConfig = {
  slug: 'media',
  admin: {
    useAsTitle: 'alt',
  },
  access: {
    // Medien sind öffentlich lesbar
    read: () => true,
    create: ({ req }) => !!req.user,
    update: ({ req }) => !!req.user,
    delete: ({ req }) => !!req.user,
  },
  upload: {
    staticDir: 'media',
    mimeTypes: ['image/*', 'application/pdf'],
  },
  fields: [
    {
      name: 'alt',
      type: 'text',
      required: true,
    },
  ],
}

Categories Collection (src/collections/Categories.ts)

access: {
  read: () => true, // Kategorien sind öffentlich
  create: ({ req }) => !!req.user,
  update: ({ req }) => !!req.user,
  delete: ({ req }) => !!req.user,
},
access: {
  read: () => true, // Social Links sind öffentlich
  create: ({ req }) => !!req.user,
  update: ({ req }) => !!req.user,
  delete: ({ req }) => !!req.user,
},

Globals öffentlich machen

SiteSettings (src/globals/SiteSettings.ts)

import type { GlobalConfig } from 'payload'

export const SiteSettings: GlobalConfig = {
  slug: 'site-settings',
  access: {
    read: () => true, // Öffentlich lesbar
    update: ({ req }) => !!req.user,
  },
  fields: [
    // ... bestehende Felder
  ],
}

Navigation (src/globals/Navigation.ts)

import type { GlobalConfig } from 'payload'

export const Navigation: GlobalConfig = {
  slug: 'navigation',
  access: {
    read: () => true, // Öffentlich lesbar
    update: ({ req }) => !!req.user,
  },
  fields: [
    // ... bestehende Felder
  ],
}

Schritt 3: GraphQL aktivieren (Optional)

Falls GraphQL gewünscht ist, installiere das Plugin:

pnpm add @payloadcms/graphql

Dann in payload.config.ts:

import { buildConfig } from 'payload'
import { graphqlPlugin } from '@payloadcms/graphql'

export default buildConfig({
  // ... andere Config
  
  plugins: [
    graphqlPlugin({}),
    multiTenantPlugin({
      // ...
    }),
  ],
})

GraphQL Endpoint: https://pl.c2sgmbh.de/api/graphql

Schritt 4: API Key für geschützte Operationen (Optional)

Für Operationen wie Kontaktformular-Submissions kann ein API Key erstellt werden.

Umgebungsvariable hinzufügen

# In .env hinzufügen
PAYLOAD_API_KEY=dein-sicherer-api-key-hier-generieren

Generiere einen sicheren Key:

openssl rand -hex 32

API Key Middleware (falls benötigt)

Für spezielle Endpoints kann der API Key geprüft werden. Für die meisten Fälle reicht jedoch die Access Control.

Schritt 5: Media URL Konfiguration

Stelle sicher, dass Media-URLs korrekt sind:

// In payload.config.ts
export default buildConfig({
  serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL || 'https://pl.c2sgmbh.de',
  
  // ... rest
})

Die .env sollte enthalten:

PAYLOAD_PUBLIC_SERVER_URL=https://pl.c2sgmbh.de

Schritt 6: Build und Neustart

cd /home/payload/payload-cms
pnpm build
pm2 restart payload

API Endpoints nach Konfiguration

Endpoint Methode Beschreibung
/api/pages GET Alle veröffentlichten Seiten
/api/pages?where[slug][equals]=home GET Seite nach Slug
/api/posts GET Alle veröffentlichten Posts
/api/posts?limit=10&page=1 GET Posts paginiert
/api/categories GET Alle Kategorien
/api/media GET Alle Medien
/api/globals/site-settings GET Site Settings
/api/globals/navigation GET Navigation

Test der API

Nach dem Neustart testen:

# Von sv-dev aus
curl https://pl.c2sgmbh.de/api/pages
curl https://pl.c2sgmbh.de/api/globals/site-settings
curl https://pl.c2sgmbh.de/api/globals/navigation

# Oder lokal auf sv-payload
curl http://localhost:3000/api/pages

Erfolgskriterien

  1. API antwortet auf Anfragen von dev.zh3.de ohne CORS-Fehler
  2. Öffentliche Endpoints liefern Daten ohne Auth
  3. Admin-Panel funktioniert weiterhin unter /admin
  4. Media-URLs sind vollständig (mit Domain)

Sicherheitshinweise

  • Nur read-Zugriff ist öffentlich
  • create, update, delete erfordern Authentifizierung
  • Unveröffentlichte Inhalte sind nicht öffentlich sichtbar
  • Admin-Panel bleibt geschützt