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>
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
- CORS konfigurieren für Frontend-Zugriff
- API-Zugriff ohne Authentifizierung für öffentliche Inhalte ermöglichen
- Optional: GraphQL aktivieren
- 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,
},
SocialLinks Collection (src/collections/SocialLinks.ts)
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
- ✅ API antwortet auf Anfragen von dev.zh3.de ohne CORS-Fehler
- ✅ Öffentliche Endpoints liefern Daten ohne Auth
- ✅ Admin-Panel funktioniert weiterhin unter /admin
- ✅ Media-URLs sind vollständig (mit Domain)
Sicherheitshinweise
- Nur
read-Zugriff ist öffentlich create,update,deleteerfordern Authentifizierung- Unveröffentlichte Inhalte sind nicht öffentlich sichtbar
- Admin-Panel bleibt geschützt