mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 20:54:11 +00:00
Add Multi-Server Orchestration (Phase 1-8) to all docs: - INFRASTRUCTURE.md: Hetzner 1/2 production servers, SSH infrastructure, payload-contracts, deployment workflows, port-forwarding - PROJECT_STATUS.md: Orchestration changelog, production URLs, SSH commands - FRONTEND.md: payload-contracts usage, CI/CD pipelines, staging/production deploy, work order system, ESLint config, updated tenant IDs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
464 lines
12 KiB
Markdown
464 lines
12 KiB
Markdown
# Frontend-Entwicklung - Payload CMS Multi-Tenant
|
|
|
|
> **Server:** sv-frontend (LXC 704) - 10.10.181.104
|
|
> **Backend API:** https://cms.c2sgmbh.de/api (Production)
|
|
> **Shared Types:** `@c2s/payload-contracts` (Git-Dependency)
|
|
|
|
## Übersicht
|
|
|
|
Jedes Frontend ist ein separates Next.js-Projekt und nutzt Payload CMS als Headless CMS über die REST-API. Alle Frontends teilen sich TypeScript-Typen und den API-Client über das `payload-contracts` Package.
|
|
|
|
**Architektur:**
|
|
```
|
|
CMS (payload-cms) payload-contracts Frontends (Next.js)
|
|
━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━
|
|
Collections + Blocks → Shared Types → frontend.porwoll.de
|
|
payload-types.ts → API Client → frontend.blogwoman.de
|
|
Block Registry → (7 weitere)
|
|
```
|
|
|
|
---
|
|
|
|
## payload-contracts (Shared Package)
|
|
|
|
Alle Frontends verwenden `@c2s/payload-contracts` als Git-Dependency für TypeScript-Typen, den API-Client und die Block-Registry.
|
|
|
|
### Installation
|
|
|
|
```json
|
|
// package.json
|
|
{
|
|
"dependencies": {
|
|
"@c2s/payload-contracts": "github:complexcaresolutions/payload-contracts#main"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Imports
|
|
|
|
```typescript
|
|
// Types
|
|
import type { Page, Post, Media } from '@c2s/payload-contracts/types'
|
|
import type { Block, BlockByType } from '@c2s/payload-contracts/types'
|
|
|
|
// API Client
|
|
import { createPayloadClient } from '@c2s/payload-contracts/api-client'
|
|
|
|
// Block Renderer
|
|
import { createBlockRenderer } from '@c2s/payload-contracts/blocks'
|
|
|
|
// Constants
|
|
import { TENANTS } from '@c2s/payload-contracts/constants'
|
|
```
|
|
|
|
### API Client erstellen
|
|
|
|
```typescript
|
|
// src/lib/api.ts
|
|
import { createPayloadClient } from '@c2s/payload-contracts/api-client'
|
|
|
|
export const api = createPayloadClient({
|
|
baseUrl: process.env.NEXT_PUBLIC_PAYLOAD_URL!,
|
|
tenantId: Number(process.env.NEXT_PUBLIC_TENANT_ID),
|
|
})
|
|
|
|
// Verwendung
|
|
const page = await api.pages.getBySlug('home')
|
|
const posts = await api.posts.getAll({ limit: 10 })
|
|
const nav = await api.navigation.get()
|
|
const settings = await api.settings.get()
|
|
```
|
|
|
|
### Block Renderer
|
|
|
|
```typescript
|
|
// src/components/blocks/index.tsx
|
|
import { createBlockRenderer } from '@c2s/payload-contracts/blocks'
|
|
import { HeroBlock } from './HeroBlock'
|
|
import { TextBlock } from './TextBlock'
|
|
// ... weitere Block-Imports
|
|
|
|
export const BlockRenderer = createBlockRenderer({
|
|
'hero-block': HeroBlock,
|
|
'text-block': TextBlock,
|
|
// ... nur die Blocks registrieren, die das Frontend braucht
|
|
})
|
|
```
|
|
|
|
### Block-Komponente implementieren
|
|
|
|
```typescript
|
|
// src/components/blocks/HeroBlock.tsx
|
|
import type { BlockByType } from '@c2s/payload-contracts/types'
|
|
|
|
type HeroBlockData = BlockByType<'hero-block'>
|
|
|
|
interface HeroBlockProps {
|
|
block: HeroBlockData
|
|
}
|
|
|
|
export function HeroBlock({ block }: HeroBlockProps) {
|
|
return (
|
|
<section>
|
|
<h1>{block.headline}</h1>
|
|
{block.subline && <p>{block.subline}</p>}
|
|
</section>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Types aktualisieren
|
|
|
|
Wenn sich CMS-Collections oder Blocks ändern:
|
|
|
|
```bash
|
|
# Auf sv-payload:
|
|
cd ~/payload-cms && pnpm payload generate:types
|
|
cd ~/payload-contracts && pnpm extract
|
|
git add -A && git commit -m "types: update from CMS" && git push
|
|
|
|
# Auf sv-frontend (pro Projekt):
|
|
cd ~/frontend.porwoll.de
|
|
pnpm update @c2s/payload-contracts
|
|
```
|
|
|
|
---
|
|
|
|
## Umgebungskonfiguration
|
|
|
|
### Environment Variables (.env.local)
|
|
|
|
```env
|
|
# API-Endpunkte (PRODUKTION)
|
|
NEXT_PUBLIC_PAYLOAD_URL=https://cms.c2sgmbh.de
|
|
NEXT_PUBLIC_API_URL=https://cms.c2sgmbh.de/api
|
|
|
|
# Analytics (optional)
|
|
NEXT_PUBLIC_UMAMI_HOST=https://analytics.c2sgmbh.de
|
|
NEXT_PUBLIC_UMAMI_WEBSITE_ID=<website-id>
|
|
|
|
# Tenant-Konfiguration (je nach Projekt)
|
|
NEXT_PUBLIC_TENANT_ID=4
|
|
NEXT_PUBLIC_TENANT_SLUG=c2s
|
|
```
|
|
|
|
### Tenant-IDs
|
|
|
|
| ID | Name | Slug | Domain | Status |
|
|
|----|------|------|--------|--------|
|
|
| 1 | porwoll.de | porwoll | porwoll.de | ✅ Live |
|
|
| 4 | Complex Care Solutions GmbH | c2s | complexcaresolutions.de | Geplant |
|
|
| 5 | Gunshin | gunshin | gunshin.de | Geplant |
|
|
| 9 | BlogWoman | blogwoman | blogwoman.de | ✅ Live |
|
|
|
|
### Warum Production-Daten?
|
|
|
|
Die Frontend-Entwicklung verwendet die Produktions-API, um mit echten Inhalten zu arbeiten. SEO-Einstellungen und Cookie-Consent-Konfigurationen werden ebenfalls aus der Produktionsumgebung geladen.
|
|
|
|
---
|
|
|
|
## CI/CD Pipeline
|
|
|
|
### CI (Lint + Build)
|
|
|
|
Jedes Frontend hat eine GitHub Actions CI-Pipeline, die bei Push auf `develop` und `main` läuft.
|
|
|
|
```yaml
|
|
# .github/workflows/ci.yml
|
|
name: CI
|
|
on:
|
|
push:
|
|
branches: [develop, main]
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: pnpm/action-setup@v4
|
|
- uses: actions/setup-node@v4
|
|
with: { node-version: 22, cache: pnpm }
|
|
- run: pnpm install --frozen-lockfile
|
|
- run: pnpm lint
|
|
- run: pnpm build
|
|
```
|
|
|
|
### Staging-Deploy (sv-frontend)
|
|
|
|
Push auf `develop` → GitHub Actions → SSH via UDM Pro SE Port-Forward → Build auf sv-frontend.
|
|
|
|
```yaml
|
|
# .github/workflows/deploy-staging.yml
|
|
# SSH-Credentials als Repository-Secrets: SSH_HOST, SSH_PORT, SSH_USER, SSH_PRIVATE_KEY
|
|
```
|
|
|
|
### Production-Deploy (Plesk)
|
|
|
|
Push auf `main` → GitHub Webhook → Plesk Git Pull → `pnpm install && pnpm build` → Passenger Restart.
|
|
|
|
Production-Deployment wird NICHT über GitHub Actions gesteuert, sondern über Plesk Git-Integration mit Webhooks.
|
|
|
|
### Deployment-Flow
|
|
|
|
```
|
|
develop (sv-frontend) → CI ✅ → Staging-Test (*-dev.porwoll.tech)
|
|
│
|
|
▼
|
|
main (merge) → CI ✅ → Webhook → Plesk → Production ✅
|
|
```
|
|
|
|
---
|
|
|
|
## Work-Order-System
|
|
|
|
Wenn ein neuer Block oder eine neue Collection im CMS erstellt wird, koordiniert das Work-Order-System die Frontend-Implementierung.
|
|
|
|
### Ablauf
|
|
|
|
1. **sv-payload:** CMS ändern → Types extrahieren → Work Order schreiben
|
|
2. **sv-frontend:** Work Order lesen → `pnpm update @c2s/payload-contracts` → Block implementieren
|
|
|
|
### Referenz
|
|
|
|
Vollständige Dokumentation im `payload-contracts` Repo:
|
|
- `work-orders/_template.md` — Vorlage
|
|
- `scripts/create-work-order.sh` — Work Order erstellen (auf sv-payload)
|
|
- `scripts/execute-work-order.sh` — Work Order ausführen (auf sv-frontend)
|
|
|
|
---
|
|
|
|
## API-Dokumentation
|
|
|
|
| Ressource | URL |
|
|
|-----------|-----|
|
|
| **Swagger UI** | https://cms.c2sgmbh.de/api/docs |
|
|
| **OpenAPI JSON** | https://cms.c2sgmbh.de/api/openapi.json |
|
|
| **REST API Base** | https://cms.c2sgmbh.de/api |
|
|
|
|
---
|
|
|
|
## Frontend-Status
|
|
|
|
### Live
|
|
|
|
| Frontend | Production | Blocks | Contracts |
|
|
|----------|-----------|--------|-----------|
|
|
| porwoll.de | ✅ Hetzner 2 | 9 implementiert | Direkte Types |
|
|
| blogwoman.de | ✅ Hetzner 1 | Alle implementiert | Bridge-Pattern (lokale types.ts) |
|
|
|
|
### Geplant
|
|
|
|
| Frontend | Server | Priorität |
|
|
|----------|--------|-----------|
|
|
| complexcaresolutions.de | Hetzner 1 | Hoch |
|
|
| caroline-porwoll.com | Hetzner 2 | Mittel |
|
|
| caroline-porwoll.de | Hetzner 2 | Mittel |
|
|
| gunshin.de | - | Niedrig |
|
|
| zweitmeinu.ng | Hetzner 1 | Niedrig |
|
|
|
|
### Offene Tasks
|
|
|
|
- [ ] porwoll.de: Fehlende Blocks implementieren (~6 via Work Orders)
|
|
- [ ] blogwoman.de: Bridge-Pattern durch direkte Contracts-Imports ersetzen
|
|
- [ ] Cookie-Banner implementieren (DSGVO)
|
|
- [ ] Newsletter-Formular-Komponente
|
|
|
|
---
|
|
|
|
## API-Endpoints
|
|
|
|
### Collections
|
|
|
|
| Collection | Endpoint | Beschreibung |
|
|
|------------|----------|--------------|
|
|
| Pages | `GET /api/pages` | Seiten mit Blocks |
|
|
| Posts | `GET /api/posts` | Blog, News, Presse |
|
|
| Categories | `GET /api/categories` | Post-Kategorien |
|
|
| Testimonials | `GET /api/testimonials` | Kundenbewertungen |
|
|
| Team | `GET /api/team` | Team-Mitglieder |
|
|
| Services | `GET /api/services` | Leistungen |
|
|
| FAQs | `GET /api/faqs` | FAQ-Einträge |
|
|
| Portfolios | `GET /api/portfolios` | Portfolio-Projekte |
|
|
| Media | `GET /api/media` | Medien/Bilder |
|
|
| Videos | `GET /api/videos` | Video-Bibliothek |
|
|
| Timelines | `GET /api/timelines` | Chronologische Events |
|
|
| Workflows | `GET /api/workflows` | Prozess-Darstellungen |
|
|
| Favorites | `GET /api/favorites` | Affiliate-Produkte (BlogWoman) |
|
|
| Series | `GET /api/series` | YouTube-Serien (BlogWoman) |
|
|
|
|
### Site Settings & Navigation (Tenant-isoliert)
|
|
|
|
| Collection | Endpoint | Beschreibung |
|
|
|------------|----------|--------------|
|
|
| Site Settings | `GET /api/site-settings?where[tenant][equals]=4` | Logo, Name, Kontakt, Adresse |
|
|
| Navigation | `GET /api/navigations?where[tenant][equals]=4` | Menü-Struktur |
|
|
| Privacy Policy | `GET /api/privacy-policy-settings?where[tenant][equals]=4` | Datenschutz |
|
|
|
|
### Spezielle Endpoints
|
|
|
|
| Endpoint | Methode | Beschreibung |
|
|
|----------|---------|--------------|
|
|
| `/api/search` | GET | Volltextsuche |
|
|
| `/api/search/suggestions` | GET | Auto-Complete |
|
|
| `/api/newsletter/subscribe` | POST | Newsletter-Anmeldung |
|
|
|
|
---
|
|
|
|
## Tenant-Filterung
|
|
|
|
Alle Collection-Anfragen sollten nach Tenant gefiltert werden:
|
|
|
|
```typescript
|
|
// Mit payload-contracts API Client (empfohlen):
|
|
const posts = await api.posts.getAll({ limit: 10 })
|
|
// → Tenant-Filter wird automatisch angewendet
|
|
|
|
// Manuell:
|
|
fetch('https://cms.c2sgmbh.de/api/posts?where[tenant][equals]=4&locale=de')
|
|
```
|
|
|
|
---
|
|
|
|
## Bild-Optimierung
|
|
|
|
Media-Objekte enthalten mehrere Größen:
|
|
|
|
```typescript
|
|
interface Media {
|
|
url: string // Original
|
|
sizes: {
|
|
thumbnail: { url, width, height } // 150x150
|
|
small: { url, width, height } // 300x300
|
|
medium: { url, width, height } // 600x600
|
|
large: { url, width, height } // 1200x1200
|
|
xlarge: { url, width, height } // 1920x1920
|
|
'2k': { url, width, height } // 2560x2560
|
|
og: { url, width, height } // 1200x630 (Social)
|
|
// + AVIF-Varianten
|
|
thumbnail_avif: { url, width, height }
|
|
small_avif: { url, width, height }
|
|
// ...
|
|
}
|
|
focalX?: number // Fokuspunkt X (0-100)
|
|
focalY?: number // Fokuspunkt Y (0-100)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Lokalisierung
|
|
|
|
Unterstützte Locales: `de` (default), `en`
|
|
|
|
```typescript
|
|
// Deutsch (default)
|
|
fetch('https://cms.c2sgmbh.de/api/posts?locale=de')
|
|
|
|
// Englisch
|
|
fetch('https://cms.c2sgmbh.de/api/posts?locale=en')
|
|
|
|
// Fallback: Wenn EN nicht vorhanden, wird DE zurückgegeben
|
|
```
|
|
|
|
---
|
|
|
|
## Newsletter-Integration
|
|
|
|
```typescript
|
|
const response = await fetch('https://cms.c2sgmbh.de/api/newsletter/subscribe', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
email: 'user@example.com',
|
|
firstName: 'Max', // optional
|
|
tenantId: 4, // Pflicht
|
|
source: 'footer' // optional: Herkunft
|
|
})
|
|
})
|
|
```
|
|
|
|
**Flow:** Subscribe → Double Opt-In E-Mail → Bestätigung → Willkommens-E-Mail
|
|
|
|
---
|
|
|
|
## Rate-Limits
|
|
|
|
| Endpoint | Limit |
|
|
|----------|-------|
|
|
| Öffentliche API | 60/min |
|
|
| Suche | 30/min |
|
|
| Newsletter | 5/10min |
|
|
| Formulare | 5/10min |
|
|
|
|
---
|
|
|
|
## Development Server (sv-frontend)
|
|
|
|
### SSH-Zugang
|
|
|
|
```bash
|
|
ssh frontend@10.10.181.104
|
|
```
|
|
|
|
### Projekt starten
|
|
|
|
```bash
|
|
cd ~/frontend.porwoll.de
|
|
pnpm dev
|
|
# Läuft auf Port 3000 → https://porwoll-dev.porwoll.tech
|
|
```
|
|
|
|
### AI-Tools
|
|
|
|
```bash
|
|
claude # Claude Code CLI (v2.1.37)
|
|
codex # Codex CLI
|
|
gemini # Gemini CLI
|
|
```
|
|
|
|
---
|
|
|
|
## ESLint-Konfiguration
|
|
|
|
Alle Frontends verwenden die gleiche ESLint-Konfiguration:
|
|
|
|
```javascript
|
|
// eslint.config.mjs
|
|
import { defineConfig, globalIgnores } from "eslint/config";
|
|
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
import nextTs from "eslint-config-next/typescript";
|
|
|
|
const eslintConfig = defineConfig([
|
|
...nextVitals,
|
|
...nextTs,
|
|
{
|
|
rules: {
|
|
"@typescript-eslint/no-unused-vars": ["warn", {
|
|
argsIgnorePattern: "^_",
|
|
varsIgnorePattern: "^_",
|
|
destructuredArrayIgnorePattern: "^_",
|
|
}],
|
|
},
|
|
},
|
|
globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts", "server.js"]),
|
|
]);
|
|
|
|
export default eslintConfig;
|
|
```
|
|
|
|
`server.js` wird ignoriert, da es CJS für Phusion Passenger verwendet.
|
|
|
|
---
|
|
|
|
## Ressourcen
|
|
|
|
| Ressource | URL/Pfad |
|
|
|-----------|----------|
|
|
| Payload CMS Docs | https://payloadcms.com/docs |
|
|
| API-Dokumentation | https://cms.c2sgmbh.de/api/docs |
|
|
| payload-contracts | https://github.com/complexcaresolutions/payload-contracts |
|
|
| Infrastruktur-Docs | `docs/INFRASTRUCTURE.md` |
|
|
| Analytics | https://analytics.c2sgmbh.de |
|
|
|
|
---
|
|
|
|
*Letzte Aktualisierung: 15.02.2026*
|