# Migration: Strapi zu Payload CMS
## Projekt: zweitmeinu.ng

**Erstellt:** 2026-02-20
**Strapi Version:** 5.x
**Ziel:** Payload CMS 3.x

---

## Inhaltsverzeichnis

1. [Executive Summary](#1-executive-summary)
2. [Content-Types / Collections](#2-content-types--collections)
3. [Components / Blocks](#3-components--blocks)
4. [API-Struktur](#4-api-struktur)
5. [Plugins & Extensions](#5-plugins--extensions)
6. [Datenbank & Relationen](#6-datenbank--relationen)
7. [Migrationsplan](#7-migrationsplan)
8. [Payload CMS Schema-Entwürfe](#8-payload-cms-schema-entwürfe)

---

## 1. Executive Summary

### Projektumfang

| Kategorie | Anzahl |
|-----------|--------|
| Content-Types | 16 |
| Components | 43 |
| API-Module | 18 |
| Plugins | 16 |
| Custom Controller Methods | 14 |
| Custom Services | 10 |
| Relationen | 25+ |

### Komplexitätsbewertung: **MITTEL-HOCH**

Hauptkomplexität liegt im:
- Termin-Buchungssystem (Appointment)
- Multi-Site-Konfiguration
- Dynamic Zone Page Builder
- Verschachtelte Component-Hierarchien

---

## 2. Content-Types / Collections

### 2.1 Übersicht aller Content-Types

| # | Content-Type | Draft/Publish | Felder | Relationen | Komplexität |
|---|--------------|---------------|--------|------------|-------------|
| 1 | appointment | Nein | 26 | 3 | Hoch |
| 2 | appointment-exception | Nein | 15 | 2 | Mittel |
| 3 | appointment-slot | Nein | 16 | 3 | Mittel |
| 4 | blog-category | Ja | 11 | 2 | Niedrig |
| 5 | blog-post | Ja | 15 | 2 | Mittel |
| 6 | contact-message | Nein | 13 | 1 | Niedrig |
| 7 | expert | Ja | 16 | 2 | Mittel |
| 8 | faq | Ja | 17 | 5 | Mittel |
| 9 | faq-category | Nein | 8 | 2 | Niedrig |
| 10 | faq-feedback | Nein | 15 | 3 | Niedrig |
| 11 | legal-page | Ja | 7 | 0 | Niedrig |
| 12 | news-item | Ja | 15 | 2 | Mittel |
| 13 | page | Ja | 5 | 1 | Hoch (Dynamic Zone) |
| 14 | service | Ja | 14 | 2 | Mittel |
| 15 | site-configuration | Ja | 25 | 5 | Hoch |
| 16 | waiting-list | Nein | 20 | 3 | Mittel |

---

### 2.2 Detaillierte Content-Type Definitionen

#### APPOINTMENT (Terminbuchungen)

```
Felder:
├── appointmentId (UUID, unique)
├── patientName (string, required, max 100)
├── patientEmail (email, required)
├── patientPhone (string, required, max 30)
├── patientDateOfBirth (date)
├── patientInsurance (string, max 100)
├── appointmentDate (date, required)
├── appointmentTime (time, required)
├── duration (integer, 15-240, default 30)
├── appointmentType (enum: consultation, examination, treatment, followup, vaccination, other)
├── status (enum: requested, confirmed, cancelled, completed, no-show)
├── confirmationToken (string, unique, private)
├── cancellationToken (string, unique, private)
├── cancellationReason (text, max 500)
├── notes (text, max 1000)
├── internalNotes (text, private)
├── reminderSent (boolean, private)
├── confirmationSent (boolean, private)
├── patientConsent (boolean, required)
├── requestedAt (datetime, private)
├── confirmedAt (datetime, private)
├── cancelledAt (datetime, private)
├── completedAt (datetime, private)
├── ipAddress (string, private)
└── userAgent (string, private)

Relationen:
├── expert → Expert (manyToOne)
├── service → Service (manyToOne)
└── site → Site-Configuration (manyToOne)
```

#### APPOINTMENT-EXCEPTION (Ausnahmen/Urlaub)

```
Felder:
├── type (enum: holiday, vacation, sick, conference, training, blocked, other)
├── startDate (datetime, required)
├── endDate (datetime, required)
├── allDay (boolean, default false)
├── reason (string, required, max 200)
├── description (text, max 500)
├── affectsAllSites (boolean, default false)
├── recurring (boolean, default false)
├── recurringPattern (enum: daily, weekly, monthly, yearly)
├── recurringEndDate (date)
├── isActive (boolean, default true)
├── notifyPatients (boolean, default true)
└── color (string, hex format)

Relationen:
├── expert → Expert (manyToOne, required)
└── site → Site-Configuration (manyToOne)
```

#### APPOINTMENT-SLOT (Verfügbare Zeitslots)

```
Felder:
├── dayOfWeek (enum: Mo-So, required)
├── startTime (time, required)
├── endTime (time, required)
├── slotDuration (integer, 15-240, default 30)
├── maxAppointments (integer, 1-10, default 1)
├── isActive (boolean, default true)
├── validFrom (date)
├── validUntil (date)
├── breakAfter (integer, 0-60, default 0)
├── bufferBefore (integer, 0-60, default 0)
├── bufferAfter (integer, 0-60, default 0)
├── color (string, hex)
└── notes (text, max 500)

Relationen:
├── expert → Expert (manyToOne, required)
├── site → Site-Configuration (manyToOne, required)
└── services ↔ Service (manyToMany)
```

#### BLOG-CATEGORY

```
Felder:
├── name (string, required, unique, max 100)
├── slug (uid, required, targetField: name)
├── description (text, max 500)
├── color (string, hex, default #0070f3)
├── icon (string, max 50)
├── order (integer, default 0)
├── isActive (boolean, default true)
└── seo (component: shared.seo)

Relationen:
├── blogPosts ← Blog-Post (oneToMany)
├── parent → Blog-Category (manyToOne, self-referential)
└── children ← Blog-Category (oneToMany, self-referential)
```

#### BLOG-POST

```
Felder:
├── title (string, required, max 200)
├── slug (uid, required, targetField: title)
├── excerpt (text, required, max 300)
├── content (richtext, required)
├── featuredImage (media, images only)
├── author (component: blog.author)
├── tags (json)
├── publishedAt (datetime)
├── readingTime (integer, min 1)
├── featured (boolean, default false)
├── relatedPosts (json)
└── seo (component: shared.seo)

Relationen:
├── category → Blog-Category (manyToOne)
└── sites ↔ Site-Configuration (manyToMany)
```

#### CONTACT-MESSAGE

```
Felder:
├── messageId (UUID, unique)
├── name (string, required, max 100)
├── email (email, required)
├── phone (string, max 30)
├── subject (string, required, max 200)
├── message (text, required, max 5000)
├── status (enum: new, read, replied, spam)
├── consentGiven (boolean, required)
├── submittedAt (datetime, private)
├── ipAddress (string, private)
└── userAgent (string, private)

Relationen:
└── site → Site-Configuration (manyToOne)
```

#### EXPERT (Ärzte/Spezialisten)

```
Felder:
├── expertId (UUID, unique)
├── firstName (string, required)
├── lastName (string, required)
├── slug (uid, required, targetField: lastName)
├── title (string)
├── specialties (json)
├── bio (richtext)
├── image (media, images only)
├── qualifications (component: expert.qualification, repeatable)
├── availability (component: expert.availability)
├── contactInfo (json)
├── isActive (boolean, default true)
├── order (integer, default 0)
└── seo (component: shared.seo)

Relationen:
├── services ↔ Service (manyToMany)
└── sites ↔ Site-Configuration (manyToMany)
```

#### FAQ

```
Felder:
├── question (string, required, max 300)
├── slug (uid, required, targetField: question)
├── answer (richtext, required)
├── shortAnswer (text, max 500)
├── tags (json)
├── priority (enum: low, medium, high, featured)
├── viewCount (integer, private)
├── helpfulCount (integer)
├── notHelpfulCount (integer)
├── attachments (media, multiple)
├── videoUrl (string)
├── searchKeywords (text, private)
├── lastUpdated (datetime)
└── seo (component: shared.seo)

Relationen:
├── category → FAQ-Category (manyToOne)
├── relatedFaqs ↔ FAQ (manyToMany, self-referential)
├── relatedServices ↔ Service (manyToMany)
├── relatedExperts ↔ Expert (manyToMany)
└── sites ↔ Site-Configuration (manyToMany)
```

#### FAQ-CATEGORY

```
Felder:
├── name (string, required, unique, max 100)
├── slug (uid, required, targetField: name)
├── description (text, max 300)
├── icon (string, default "question-circle")
├── color (string, hex, default #3B82F6)
└── order (integer, default 0)

Relationen:
├── faqs ← FAQ (oneToMany)
└── sites ↔ Site-Configuration (manyToMany)
```

#### FAQ-FEEDBACK

```
Felder:
├── isHelpful (boolean, required)
├── comment (text, max 1000)
├── additionalFeedback (enum: too_technical, not_detailed_enough, outdated, confusing, perfect, needs_examples)
├── userEmail (email)
├── ipAddress (string, private)
├── userAgent (string, private)
├── sessionId (string, private)
├── followUpRequested (boolean, default false)
├── resolved (boolean, default false)
├── resolvedAt (datetime)
└── internalNotes (text, private)

Relationen:
├── faq → FAQ (manyToOne, required)
├── site → Site-Configuration (manyToOne)
└── resolvedBy → Admin-User (manyToOne)
```

#### LEGAL-PAGE

```
Felder:
├── type (enum: impressum, datenschutz, agb, cookie-policy, required)
├── content (richtext, required)
├── country (enum: global, de, at, ch, nl, es, uk)
├── language (enum: de, en, nl, es, fr, it)
├── validFrom (date)
├── validUntil (date)
└── version (string)

Relationen: Keine
```

#### NEWS-ITEM

```
Felder:
├── title (string, required, max 150)
├── slug (uid, required, targetField: title)
├── summary (text, required, max 250)
├── content (richtext, required)
├── featuredImage (media, images only)
├── newsType (enum: announcement, update, event, press_release, medical_news)
├── priority (enum: normal, important, urgent)
├── publishDate (datetime, required)
├── expiryDate (datetime)
├── author (component: blog.author)
├── tags (json)
├── attachments (media, multiple)
├── externalLink (string)
└── seo (component: shared.seo)

Relationen:
├── sites ↔ Site-Configuration (manyToMany)
└── relatedServices ↔ Service (manyToMany)
```

#### PAGE (Dynamischer Page Builder)

```
Felder:
├── title (string, required)
├── slug (uid, required, targetField: title)
├── isSharedContent (boolean, default false)
├── sections (dynamiczone) ← KRITISCH FÜR MIGRATION
└── seo (component: shared.seo)

Dynamic Zone Components (11):
├── sections.hero
├── sections.text-block
├── sections.cta-banner
├── sections.services-grid
├── sections.team-grid
├── sections.testimonials
├── sections.expert-grid
├── sections.faq
├── sections.contact-form
├── sections.blog-list
└── sections.faq-reference

Relationen:
└── sites ↔ Site-Configuration (manyToMany)
```

#### SERVICE (Medizinische Dienstleistungen)

```
Felder:
├── name (string, required)
├── slug (uid, required, targetField: name)
├── description (richtext, required)
├── shortDescription (text, max 300)
├── icon (media, images only)
├── featuredImage (media, images only)
├── price (component: service.pricing)
├── features (component: service.feature, repeatable)
├── duration (string)
├── category (enum: diagnostic, therapeutic, preventive, surgical, emergency, consultation)
├── isActive (boolean, default true)
├── order (integer, default 0)
└── seo (component: shared.seo)

Relationen:
├── sites ↔ Site-Configuration (manyToMany)
└── experts ↔ Expert (manyToMany)
```

#### SITE-CONFIGURATION (Zentrale Domain-Konfiguration)

```
Felder:
├── siteIdentifier (string, required, unique)
├── domain (string, required)
├── siteName (string, required, max 100)
├── tagline (string, max 200)
├── logo (media, images only)
├── favicon (media, images only)
├── aliases (json)
├── brand (enum: complexcare, zweitmeinung, portal, required)
├── specialty (enum: general, intensiv, kardiologie, onkologie, chirurgie, radiologie, nephrologie, orthopaedie, polypharmazie, pflege)
├── locales (json, default: de-DE)
├── navigation (json)
├── footer (json)
├── theme (component: site.theme)
├── contact (component: site.contact)
├── features (json)
├── socialMedia (component: site.social-media)
├── analytics (component: site.analytics)
├── seo (component: shared.seo)
├── portalSettings (component: site.portal-settings)
├── maintenanceMode (component: site.maintenance-mode)
├── emailSettings (component: site.email-settings, required)
└── appointmentSettings (component: appointment.appointment-settings)

Relationen:
├── newsItems ↔ News-Item (manyToMany)
├── blogPosts ↔ Blog-Post (manyToMany)
├── services ↔ Service (manyToMany)
├── experts ↔ Expert (manyToMany)
└── pages ↔ Page (manyToMany)
```

#### WAITING-LIST

```
Felder:
├── waitingListId (UUID, unique)
├── patientName (string, required, max 100)
├── patientEmail (email, required)
├── patientPhone (string, required, max 30)
├── preferredDates (json, default [])
├── preferredTimeOfDay (enum: morning, afternoon, evening, any)
├── flexibleDates (boolean, default false)
├── priority (enum: normal, urgent, emergency)
├── status (enum: waiting, contacted, appointed, expired, cancelled)
├── notes (text, max 1000)
├── internalNotes (text, private)
├── addedAt (datetime, private)
├── contactedAt (datetime, private)
├── appointedAt (datetime, private)
├── expiresAt (datetime)
├── contactAttempts (integer, private)
├── lastContactAttempt (datetime, private)
├── patientConsent (boolean, required)
└── ipAddress (string, private)

Relationen:
├── expert → Expert (manyToOne)
├── service → Service (manyToOne)
└── site → Site-Configuration (manyToOne)
```

---

## 3. Components / Blocks

### 3.1 Component-Kategorien Übersicht

| Kategorie | Anzahl | Verwendungszweck |
|-----------|--------|------------------|
| admin | 1 | Analytics Dashboard |
| appointment | 5 | Terminverwaltung-Einstellungen |
| blog | 2 | Blog-Autoren & Kategorien |
| elements | 1 | Wiederverwendbare UI-Elemente |
| expert | 2 | Qualifikationen & Verfügbarkeit |
| layout | 3 | Navigation & Footer |
| sections | 11 | Page Builder Blöcke |
| service | 2 | Preise & Features |
| shared | 3 | SEO & Meta-Daten |
| site | 12 | Site-Konfiguration |

**Gesamt: 43 Components**

---

### 3.2 Detaillierte Component-Definitionen

#### SHARED (Wiederverwendbar in mehreren Content-Types)

**shared.seo** (Verwendet in 7 Content-Types)
```
├── metaTitle (string, max 60)
├── metaDescription (text, max 160)
├── keywords (text)
├── canonicalURL (string)
├── structuredData (json)
├── metaImage (media, images only)
├── openGraph (component: shared.open-graph)
└── metaSocial (component: shared.meta-social, repeatable)
```

**shared.open-graph**
```
├── type (enum: website, article, profile, book, music, video)
├── title (string, max 95)
├── description (text, max 200)
├── image (media, images only)
├── url (string)
├── siteName (string, max 50)
└── locale (string, default "de_DE")
```

**shared.meta-social**
```
├── socialNetwork (enum: Facebook, Twitter, LinkedIn, required)
├── title (string, max 60)
├── description (text, max 160)
└── image (media, images only)
```

---

#### ELEMENTS (Basis-Bausteine)

**elements.button** (Verwendet in 7 Components)
```
├── label (string, required)
├── url (string)
├── style (enum: primary, secondary, outline)
├── icon (string)
└── openInNewTab (boolean, default false)
```

---

#### SECTIONS (Page Builder Blöcke)

**sections.hero**
```
├── title (string)
├── subtitle (text)
├── backgroundImage (media)
├── alignment (enum: left, center, right)
└── buttons (component: elements.button, repeatable)
```

**sections.text-block**
```
├── content (richtext, required)
├── layout (enum: full-width, centered, two-columns)
└── backgroundColor (enum: white, light, dark, primary)
```

**sections.cta-banner**
```
├── title (string, required)
├── description (text)
├── button (component: elements.button)
└── backgroundStyle (enum: gradient, solid, image)
```

**sections.services-grid**
```
├── title (string)
├── subtitle (text)
├── serviceIds (json)
├── displayMode (enum: all, selected, category)
├── category (enum: diagnostic, therapeutic, preventive, surgical, emergency, consultation)
├── columns (integer, 2-4)
├── showPrices (boolean)
└── cta (component: elements.button)
```

**sections.team-grid**
```
├── title (string)
├── subtitle (text)
├── members (component: sections.team-member, repeatable)
└── columns (integer, 2-4)
```

**sections.team-member**
```
├── name (string, required)
├── role (string, required)
├── bio (text)
├── image (media, images only)
└── socialLinks (json)
```

**sections.testimonials**
```
├── title (string)
├── subtitle (text)
└── testimonials (component: sections.testimonial, repeatable)
```

**sections.testimonial**
```
├── content (text, required)
├── author (string, required)
├── role (string)
├── company (string)
├── rating (integer, 1-5)
└── image (media, images only)
```

**sections.expert-grid**
```
├── title (string)
├── subtitle (text)
├── expertIds (json)
├── displayMode (enum: all, selected, specialty)
├── specialty (string)
├── columns (integer, 2-4)
├── showAvailability (boolean)
└── cta (component: elements.button)
```

**sections.faq**
```
├── title (string)
├── subtitle (text)
└── questions (component: sections.faq-item, repeatable)
```

**sections.faq-item**
```
├── question (string, required)
└── answer (richtext, required)
```

**sections.faq-reference** (Referenziert zentrales FAQ-System)
```
├── title (string)
├── subtitle (text)
├── displayMode (enum: accordion, list, cards, compact)
├── source (enum: selected, category, featured, popular)
├── selectedFaqs (json)
├── categorySlug (string)
├── limit (integer, 1-50)
├── showSearch (boolean)
├── showCategories (boolean)
└── cta (component: elements.button)
```

**sections.blog-list**
```
├── title (string)
├── subtitle (text)
├── contentType (enum: blog_posts, news_items, both)
├── displayMode (enum: grid, list, featured)
├── itemsToShow (integer, 1-12)
├── filterByCategory (string)
├── showPagination (boolean)
└── cta (component: elements.button)
```

**sections.contact-form**
```
├── title (string)
├── subtitle (text)
├── formFields (json)
├── submitButtonText (string)
├── successMessage (text)
├── recipientEmail (email)
└── consentText (text)
```

---

#### SITE (Konfiguration)

**site.theme**
```
├── primaryColor (string, hex, default #2563eb)
├── secondaryColor (string, hex, default #10b981)
├── accentColor (string, hex, default #f59e0b)
├── darkColor (string, hex, default #1f2937)
├── backgroundColor (string, hex, default #f3f4f6)
├── fontFamily (string, default "Inter, sans-serif")
└── customCSS (text)
```

**site.contact**
```
├── email (email, required)
├── phone (string)
├── fax (string)
├── address (text)
├── emergencyHotline (string)
└── openingHours (json)
```

**site.social-media**
```
├── facebook (string, regex validated)
├── twitter (string, regex validated)
├── linkedin (string, regex validated)
├── instagram (string, regex validated)
├── youtube (string, regex validated)
├── xing (string, regex validated)
├── tiktok (string, regex validated)
├── whatsapp (string)
└── telegram (string)
```

**site.analytics**
```
├── googleAnalyticsId (string, regex: UA-* or G-*)
├── googleTagManagerId (string, regex: GTM-*)
├── facebookPixelId (string)
├── matomo (json)
├── hotjar (json)
├── clarity (string)
├── cookieConsent (json)
└── customScripts (json)
```

**site.email-settings**
```
├── fromEmail (email, required)
├── fromName (string, required)
├── replyToEmail (email, required)
├── notificationEmail (email, required)
├── ccEmails (json)
├── emailSignature (richtext)
├── emailFooter (text)
├── notificationSettings (json)
└── emailTemplateStyle (json)
```

**site.maintenance-mode**
```
├── enabled (boolean, required)
├── startDate (datetime)
├── endDate (datetime)
├── title (string)
├── message (richtext, required)
├── allowedIPs (json, private)
├── allowedRoles (json)
├── showProgressBar (boolean)
├── progressPercentage (integer, 0-100)
├── contactInfo (json)
├── customCSS (text)
└── redirectUrl (string)
```

**site.portal-settings**
```
├── isPortal (boolean, required)
├── aggregatedDomains (component: site.domain-reference, repeatable)
├── showDomainGrid (boolean)
├── showLiveStatistics (boolean)
├── showExpertCarousel (boolean)
├── enableGlobalSearch (boolean)
├── showTrustIndicators (boolean)
├── statisticsConfig (component: site.statistics-config)
├── quickAccessCategories (json)
├── featuredExperts (json)
└── highlightedServices (json)
```

**site.domain-reference**
```
├── domain (string, required)
├── siteIdentifier (string, required)
├── displayName (string)
├── category (enum)
├── order (integer)
└── isActive (boolean)
```

**site.statistics-config**
```
├── showTotalOpinions (boolean)
├── showActiveExperts (boolean)
├── showSatisfactionRate (boolean)
├── showResponseTime (boolean)
├── refreshInterval (integer)
├── animateNumbers (boolean)
└── displayFormat (enum: compact, detailed, minimal)
```

---

#### APPOINTMENT (Terminverwaltung)

**appointment.appointment-settings**
```
├── bookingAdvanceDays (integer, 1-365)
├── minBookingHours (integer, 0-168)
├── maxBookingDays (integer, 1-365)
├── cancellationHours (integer, 0-168)
├── defaultDuration (integer, 15-240)
├── workingHours (component: appointment.working-hours)
├── bookingRules (json)
├── reminderHours (integer, 0-168)
├── allowWaitingList (boolean)
├── requireConfirmation (boolean)
├── requirePayment (boolean)
├── autoConfirmHours (integer)
├── maxAppointmentsPerDay (integer)
├── bufferBetweenAppointments (integer)
├── allowOnlineBooking (boolean)
├── showPrices (boolean)
├── termsAndConditions (richtext)
├── emailTemplates (component: appointment.email-templates)
├── smsEnabled (boolean)
└── notificationChannels (json)
```

**appointment.working-hours**
```
├── monday (component: appointment.day-schedule)
├── tuesday (component: appointment.day-schedule)
├── wednesday (component: appointment.day-schedule)
├── thursday (component: appointment.day-schedule)
├── friday (component: appointment.day-schedule)
├── saturday (component: appointment.day-schedule)
└── sunday (component: appointment.day-schedule)
```

**appointment.day-schedule**
```
├── isOpen (boolean)
├── openTime (time)
├── closeTime (time)
└── breaks (component: appointment.break-time, repeatable)
```

**appointment.break-time**
```
├── startTime (time, required)
├── endTime (time, required)
└── description (string)
```

**appointment.email-templates**
```
├── bookingConfirmation (richtext)
├── appointmentReminder (richtext)
├── cancellationConfirmation (richtext)
├── waitingListNotification (richtext)
├── noShowFollowUp (richtext)
├── appointmentRescheduled (richtext)
├── senderName (string)
├── senderEmail (email, required)
└── replyToEmail (email)
```

---

#### EXPERT

**expert.qualification**
```
├── title (string, required)
├── institution (string)
├── year (integer)
├── description (text)
└── type (enum: degree, certification, specialization, award, membership)
```

**expert.availability**
```
├── schedule (json)
├── consultationTypes (json)
├── locations (json)
├── bookingUrl (string)
├── responseTime (string)
└── languages (json)
```

---

#### BLOG

**blog.author**
```
├── name (string, required)
├── bio (text, max 500)
├── avatar (media, images only)
├── role (string)
└── socialLinks (json)
```

---

#### SERVICE

**service.pricing**
```
├── basePrice (decimal, required)
├── currency (string, default EUR)
├── priceNote (text)
├── isStartingPrice (boolean)
└── paymentOptions (json)
```

**service.feature**
```
├── title (string, required)
├── description (text)
└── icon (string)
```

---

## 4. API-Struktur

### 4.1 API-Endpoints Übersicht

| Modul | Custom Logic | Endpoints | Authentifizierung |
|-------|--------------|-----------|-------------------|
| appointment | Ja (14 Methods) | 13 | Nein (Token-basiert) |
| appointment-exception | Nein | 5 | Nein |
| appointment-slot | Nein | 5 | Nein |
| blog-category | Ja (1 Method) | 6 | Nein |
| blog-post | Ja (2 Methods) | 7 | Optional |
| contact-message | Ja (4 Methods) | 8 | Nein |
| expert | Nein | 5 | Optional |
| faq | Nein | 5 | Nein |
| faq-category | Nein | 5 | Nein |
| faq-feedback | Nein | 5 | Nein |
| legal-page | Nein | 5 | Optional |
| news-item | Nein | 5 | Optional |
| page | Nein | 5 | Optional |
| service | Nein | 5 | Optional |
| site-configuration | Nein | 5 | Optional |
| test-email | Ja (2 Methods) | 2 | Nein |
| waiting-list | Nein | 5 | Nein |

---

### 4.2 Custom API-Logik (Migration erforderlich)

#### Appointment Controller (14 Custom Methods)

```typescript
// Kritische Business Logic für Payload Migration

1. getAvailability(expert, service, startDate, endDate)
   - Berechnet verfügbare Zeitslots
   - Berücksichtigt: Slots, Exceptions, bestehende Termine
   - Filtert nach Buchungsfenster (24h-90d)

2. bookAppointment(patientData, appointmentDetails)
   - Validiert alle Felder
   - Prüft Slot-Verfügbarkeit
   - Generiert Confirmation/Cancellation Tokens
   - Sendet Bestätigungs-Email

3. confirmAppointment(token)
   - Validiert Token
   - Setzt Status auf 'confirmed'
   - Sendet Bestätigungs-Email

4. cancelAppointment(token, reason)
   - Prüft Stornierungsrichtlinie (24h)
   - Setzt Status auf 'cancelled'
   - Verarbeitet Warteliste
   - Benachrichtigt nächsten Patienten

5. rescheduleAppointment(appointmentId, newSlot)
   - Validiert neuen Slot
   - Aktualisiert Termin
   - Sendet Benachrichtigung

6. addToWaitingList(patientData, preferences)
   - Erstellt Wartelisten-Eintrag
   - Sendet Bestätigung

7. getCalendarView(expert, view, date)
   - Erstellt Kalender-Übersicht
   - Gruppiert nach Tag/Woche/Monat

8. getStatistics(period, expert)
   - Berechnet Statistiken
   - Status-Verteilung, No-Show-Rate, etc.

9. sendReminders()
   - Daily Job für morgige Termine
   - Setzt reminderSent Flag

10. exportAppointments(format, filters)
    - CSV-Export
    - Gefiltert nach Datum/Expert/Status

11-14. Email Services
    - sendConfirmationEmail()
    - sendCancellationEmail()
    - sendReminderEmail()
    - sendWaitingListNotification()
```

#### Contact-Message Controller (4 Custom Methods)

```typescript
1. create(data) - mit Spam-Schutz
   - Honeypot-Feld Prüfung
   - Minimum 3 Sekunden Ausfüllzeit
   - Email-Benachrichtigung an Site-Admin

2. markAsRead(id)
   - Setzt Status auf 'read'

3. markAsSpam(id)
   - Setzt Status auf 'spam'

4. stats()
   - Statistiken der letzten 30 Tage
```

#### Blog-Post Controller (2 Custom Methods)

```typescript
1. findRelated(postId)
   - Findet ähnliche Posts nach Kategorie

2. findByCategory(categorySlug, pagination)
   - Filtert Posts nach Kategorie-Slug
```

---

### 4.3 Middleware

**Contact Rate Limiting** (Optional)
```typescript
// /src/middlewares/contact-rate-limit.ts
{
  limit: 5,           // Anfragen
  interval: 60,       // Sekunden
  tracking: 'ip+path'
}
```

---

## 5. Plugins & Extensions

### 5.1 Installierte Plugins

| Plugin | Version | Payload Äquivalent |
|--------|---------|-------------------|
| @strapi/plugin-documentation | 5.18.0 | Built-in API Docs |
| @strapi/plugin-graphql | 5.18.0 | @payloadcms/plugin-graphql |
| @strapi/plugin-users-permissions | 5.18.0 | Built-in Auth |
| @strapi/plugin-seo | 2.0.8 | @payloadcms/plugin-seo |
| @strapi/provider-email-nodemailer | 5.18.0 | Nodemailer via Hooks |
| @_sh/strapi-plugin-ckeditor | 6.0.2 | Lexical Editor (built-in) |
| strapi-plugin-navigation | 3.0.16 | @payloadcms/plugin-nested-docs |
| strapi-plugin-superfields | 5.8.2 | Custom Field Types |
| strapi-plugin-multi-select | 2.1.1 | Select Field (built-in) |
| strapi-plugin-country-select | 2.1.0 | Custom Select |
| strapi-plugin-bold-title-editor | 2.0.0 | Custom RichText Config |
| strapi-5-sitemap-plugin | 1.0.7 | afterChange Hook |
| strapi-cache | 1.5.5 | Redis Plugin |
| strapi-advanced-uuid | 2.1.1 | Custom ID Field |
| strapi-5-plugin-responsive-backend | 0.0.4 | Built-in Responsive UI |
| @chartbrew/plugin-strapi | 3.0.0 | External Integration |

### 5.2 Plugin-Konfigurationen

**CKEditor 5 Toolbar:**
```
Heading | Bold, Italic, Underline, Strikethrough |
BulletedList, NumberedList | Blockquote |
Table | Code, CodeBlock | Link | Image
```

**Navigation Plugin:**
```typescript
{
  contentTypes: ["page", "service", "expert", "blog-post", "faq"],
  allowedLevels: 3
}
```

**Cache Plugin:**
```typescript
{
  type: "mem",
  maxAge: 3600000,
  cacheRoutes: ["/api/services", "/api/experts", "/api/faq", "/api/blog-posts"]
}
```

---

## 6. Datenbank & Relationen

### 6.1 Relationen-Diagramm

```
┌─────────────────────────────────────────────────────────────────┐
│                     SITE-CONFIGURATION                          │
│                    (Zentrale Konfiguration)                     │
└─────────────────────────────────────────────────────────────────┘
         │         │         │         │         │
         ▼         ▼         ▼         ▼         ▼
    ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
    │ Expert │ │Service │ │  Page  │ │BlogPost│ │NewsItem│
    └────────┘ └────────┘ └────────┘ └────────┘ └────────┘
         │         │                      │
         │         │                      ▼
         │         │               ┌─────────────┐
         │         │               │BlogCategory │
         │         │               └─────────────┘
         │         │
         ▼         ▼
    ┌─────────────────────────────────────────┐
    │              APPOINTMENT                 │
    │         (Termin-Buchungen)              │
    └─────────────────────────────────────────┘
         │
         ├──────────────────┐
         ▼                  ▼
    ┌───────────────┐ ┌─────────────────────┐
    │Appointment-   │ │ Appointment-        │
    │Exception      │ │ Slot                │
    └───────────────┘ └─────────────────────┘

    ┌─────────────────────────────────────────┐
    │              FAQ SYSTEM                  │
    └─────────────────────────────────────────┘
    ┌────────────┐     ┌────────┐     ┌─────────────┐
    │FAQ-Category│ ──► │  FAQ   │ ◄── │FAQ-Feedback │
    └────────────┘     └────────┘     └─────────────┘
                           │
                           ▼
              ┌─────────────────────┐
              │  Related: Service,  │
              │  Expert, FAQ        │
              └─────────────────────┘

    ┌─────────────────────────────────────────┐
    │           CONTACT & WAITING             │
    └─────────────────────────────────────────┘
    ┌────────────────┐     ┌─────────────┐
    │Contact-Message │     │Waiting-List │
    │     → Site     │     │→Expert,Svc  │
    └────────────────┘     └─────────────┘
```

### 6.2 Bidirektionale Relationen (ManyToMany)

| Relation | Seite A | Seite B |
|----------|---------|---------|
| 1 | Site-Configuration | Expert |
| 2 | Site-Configuration | Service |
| 3 | Site-Configuration | Page |
| 4 | Site-Configuration | Blog-Post |
| 5 | Site-Configuration | News-Item |
| 6 | Site-Configuration | FAQ |
| 7 | Site-Configuration | FAQ-Category |
| 8 | Expert | Service |
| 9 | FAQ | FAQ (self-referential) |
| 10 | FAQ | Service |
| 11 | FAQ | Expert |
| 12 | Blog-Category | Blog-Category (self-referential) |

---

## 7. Migrationsplan

### 7.1 Phase 1: Payload CMS Setup (Woche 1)

```bash
# Installation
npx create-payload-app@latest zweitmeinu-ng-payload
cd zweitmeinu-ng-payload

# Plugins
npm install @payloadcms/plugin-graphql
npm install @payloadcms/plugin-seo
npm install @payloadcms/plugin-nested-docs
npm install @payloadcms/plugin-form-builder
npm install @payloadcms/richtext-lexical
```

### 7.2 Phase 2: Collections erstellen (Woche 2-3)

**Priorität 1 - Basis Collections:**
1. Users (Admin)
2. Media
3. Site-Configuration → Sites
4. Legal-Page → LegalPages

**Priorität 2 - Content Collections:**
5. Expert → Experts
6. Service → Services
7. FAQ-Category → FAQCategories
8. FAQ → FAQs
9. Blog-Category → BlogCategories
10. Blog-Post → BlogPosts
11. News-Item → NewsItems

**Priorität 3 - Page Builder:**
12. Page → Pages (mit Blocks)

**Priorität 4 - Appointment System:**
13. Appointment → Appointments
14. Appointment-Slot → AppointmentSlots
15. Appointment-Exception → AppointmentExceptions
16. Waiting-List → WaitingList

**Priorität 5 - Formulare:**
17. Contact-Message → ContactMessages
18. FAQ-Feedback → FAQFeedback

### 7.3 Phase 3: Blocks erstellen (Woche 3)

```typescript
// /src/blocks/index.ts
export const blocks = [
  HeroBlock,
  TextBlock,
  CTABanner,
  ServicesGrid,
  TeamGrid,
  Testimonials,
  ExpertGrid,
  FAQSection,
  FAQReference,
  BlogList,
  ContactForm,
];
```

### 7.4 Phase 4: Custom Endpoints (Woche 4)

```typescript
// /src/endpoints/appointments.ts
export const appointmentEndpoints = {
  getAvailability,
  bookAppointment,
  confirmAppointment,
  cancelAppointment,
  rescheduleAppointment,
  getCalendarView,
  getStatistics,
  sendReminders,
  exportAppointments,
};
```

### 7.5 Phase 5: Daten-Migration (Woche 5)

```typescript
// Migration Script Struktur
const migrationOrder = [
  'site-configuration',
  'expert',
  'service',
  'faq-category',
  'faq',
  'blog-category',
  'blog-post',
  'news-item',
  'legal-page',
  'page',
  'appointment-slot',
  'appointment-exception',
  'appointment',
  'waiting-list',
  'contact-message',
  'faq-feedback',
];
```

### 7.6 Phase 6: Testing & Go-Live (Woche 6)

1. API-Kompatibilitätstests
2. Frontend-Integration
3. Performance-Tests
4. Staging-Deployment
5. Production-Migration

---

## 8. Payload CMS Schema-Entwürfe

### 8.1 Beispiel: Appointments Collection

```typescript
// /src/collections/Appointments.ts
import { CollectionConfig } from 'payload/types';

export const Appointments: CollectionConfig = {
  slug: 'appointments',
  admin: {
    useAsTitle: 'patientName',
    defaultColumns: ['patientName', 'appointmentDate', 'status', 'expert'],
    group: 'Termine',
  },
  access: {
    read: () => true,
    create: () => true,
    update: ({ req: { user } }) => Boolean(user),
    delete: ({ req: { user } }) => Boolean(user),
  },
  fields: [
    {
      name: 'appointmentId',
      type: 'text',
      unique: true,
      admin: { readOnly: true },
      hooks: {
        beforeChange: [({ value }) => value || crypto.randomUUID()],
      },
    },
    {
      type: 'row',
      fields: [
        { name: 'patientName', type: 'text', required: true, maxLength: 100 },
        { name: 'patientEmail', type: 'email', required: true },
        { name: 'patientPhone', type: 'text', required: true, maxLength: 30 },
      ],
    },
    {
      type: 'row',
      fields: [
        { name: 'appointmentDate', type: 'date', required: true },
        { name: 'appointmentTime', type: 'text', required: true }, // HH:MM format
        { name: 'duration', type: 'number', min: 15, max: 240, defaultValue: 30 },
      ],
    },
    {
      name: 'appointmentType',
      type: 'select',
      required: true,
      options: [
        { label: 'Beratung', value: 'consultation' },
        { label: 'Untersuchung', value: 'examination' },
        { label: 'Behandlung', value: 'treatment' },
        { label: 'Nachsorge', value: 'followup' },
        { label: 'Impfung', value: 'vaccination' },
        { label: 'Sonstiges', value: 'other' },
      ],
    },
    {
      name: 'status',
      type: 'select',
      required: true,
      defaultValue: 'requested',
      options: [
        { label: 'Angefragt', value: 'requested' },
        { label: 'Bestätigt', value: 'confirmed' },
        { label: 'Storniert', value: 'cancelled' },
        { label: 'Abgeschlossen', value: 'completed' },
        { label: 'Nicht erschienen', value: 'no-show' },
      ],
    },
    {
      name: 'expert',
      type: 'relationship',
      relationTo: 'experts',
    },
    {
      name: 'service',
      type: 'relationship',
      relationTo: 'services',
    },
    {
      name: 'site',
      type: 'relationship',
      relationTo: 'sites',
    },
    {
      name: 'tokens',
      type: 'group',
      admin: { condition: () => false }, // Hidden in admin
      fields: [
        { name: 'confirmationToken', type: 'text' },
        { name: 'cancellationToken', type: 'text' },
      ],
    },
    {
      name: 'notes',
      type: 'textarea',
      maxLength: 1000,
    },
    {
      name: 'internalNotes',
      type: 'textarea',
      admin: { condition: ({ req: { user } }) => Boolean(user) },
    },
    {
      name: 'patientConsent',
      type: 'checkbox',
      required: true,
    },
    {
      name: 'timestamps',
      type: 'group',
      admin: { readOnly: true },
      fields: [
        { name: 'requestedAt', type: 'date' },
        { name: 'confirmedAt', type: 'date' },
        { name: 'cancelledAt', type: 'date' },
        { name: 'completedAt', type: 'date' },
      ],
    },
  ],
  hooks: {
    beforeChange: [
      async ({ data, operation }) => {
        if (operation === 'create') {
          data.timestamps = { requestedAt: new Date() };
          data.tokens = {
            confirmationToken: crypto.randomUUID(),
            cancellationToken: crypto.randomUUID(),
          };
        }
        return data;
      },
    ],
    afterChange: [
      async ({ doc, operation }) => {
        if (operation === 'create') {
          // Send confirmation email
          await sendAppointmentConfirmationEmail(doc);
        }
      },
    ],
  },
};
```

### 8.2 Beispiel: Page Collection mit Blocks

```typescript
// /src/collections/Pages.ts
import { CollectionConfig } from 'payload/types';
import { blocks } from '../blocks';

export const Pages: CollectionConfig = {
  slug: 'pages',
  admin: {
    useAsTitle: 'title',
    group: 'Content',
  },
  versions: {
    drafts: true,
  },
  fields: [
    {
      name: 'title',
      type: 'text',
      required: true,
    },
    {
      name: 'slug',
      type: 'text',
      required: true,
      unique: true,
      admin: {
        position: 'sidebar',
      },
      hooks: {
        beforeValidate: [({ value, data }) =>
          value || data?.title?.toLowerCase().replace(/\s+/g, '-')
        ],
      },
    },
    {
      name: 'isSharedContent',
      type: 'checkbox',
      defaultValue: false,
      admin: { position: 'sidebar' },
    },
    {
      name: 'sections',
      type: 'blocks',
      blocks,
    },
    {
      name: 'sites',
      type: 'relationship',
      relationTo: 'sites',
      hasMany: true,
      admin: { position: 'sidebar' },
    },
    {
      name: 'seo',
      type: 'group',
      fields: [
        { name: 'metaTitle', type: 'text', maxLength: 60 },
        { name: 'metaDescription', type: 'textarea', maxLength: 160 },
        { name: 'metaImage', type: 'upload', relationTo: 'media' },
      ],
    },
  ],
};
```

### 8.3 Beispiel: Hero Block

```typescript
// /src/blocks/Hero.ts
import { Block } from 'payload/types';

export const HeroBlock: Block = {
  slug: 'hero',
  labels: {
    singular: 'Hero',
    plural: 'Heroes',
  },
  fields: [
    {
      name: 'title',
      type: 'text',
    },
    {
      name: 'subtitle',
      type: 'textarea',
    },
    {
      name: 'backgroundImage',
      type: 'upload',
      relationTo: 'media',
    },
    {
      name: 'alignment',
      type: 'select',
      defaultValue: 'center',
      options: [
        { label: 'Links', value: 'left' },
        { label: 'Zentriert', value: 'center' },
        { label: 'Rechts', value: 'right' },
      ],
    },
    {
      name: 'buttons',
      type: 'array',
      fields: [
        { name: 'label', type: 'text', required: true },
        { name: 'url', type: 'text' },
        {
          name: 'style',
          type: 'select',
          options: ['primary', 'secondary', 'outline'],
        },
        { name: 'openInNewTab', type: 'checkbox' },
      ],
    },
  ],
};
```

---

## Anhang: Checkliste für Migration

### Content-Types → Collections
- [ ] appointment → Appointments
- [ ] appointment-exception → AppointmentExceptions
- [ ] appointment-slot → AppointmentSlots
- [ ] blog-category → BlogCategories
- [ ] blog-post → BlogPosts
- [ ] contact-message → ContactMessages
- [ ] expert → Experts
- [ ] faq → FAQs
- [ ] faq-category → FAQCategories
- [ ] faq-feedback → FAQFeedback
- [ ] legal-page → LegalPages
- [ ] news-item → NewsItems
- [ ] page → Pages
- [ ] service → Services
- [ ] site-configuration → Sites
- [ ] waiting-list → WaitingList

### Components → Blocks/Fields
- [ ] shared.seo → SEO Group Field
- [ ] elements.button → Button Array Field
- [ ] sections.hero → HeroBlock
- [ ] sections.text-block → TextBlock
- [ ] sections.cta-banner → CTABannerBlock
- [ ] sections.services-grid → ServicesGridBlock
- [ ] sections.team-grid → TeamGridBlock
- [ ] sections.testimonials → TestimonialsBlock
- [ ] sections.expert-grid → ExpertGridBlock
- [ ] sections.faq → FAQBlock
- [ ] sections.faq-reference → FAQReferenceBlock
- [ ] sections.blog-list → BlogListBlock
- [ ] sections.contact-form → ContactFormBlock
- [ ] site.theme → ThemeGroup
- [ ] site.contact → ContactGroup
- [ ] site.social-media → SocialMediaGroup
- [ ] site.analytics → AnalyticsGroup
- [ ] site.email-settings → EmailSettingsGroup
- [ ] appointment.appointment-settings → AppointmentSettingsGroup
- [ ] appointment.working-hours → WorkingHoursGroup
- [ ] appointment.email-templates → EmailTemplatesGroup

### Custom API Logic → Hooks/Endpoints
- [ ] Appointment Availability Calculation
- [ ] Appointment Booking Flow
- [ ] Appointment Confirmation (Token-based)
- [ ] Appointment Cancellation
- [ ] Waiting List Processing
- [ ] Email Notifications
- [ ] Statistics Calculation
- [ ] CSV Export
- [ ] Contact Form Spam Protection
- [ ] Blog Related Posts

### Plugins → Payload Äquivalente
- [ ] GraphQL Plugin
- [ ] SEO Plugin
- [ ] Nested Docs (Navigation)
- [ ] Rich Text Editor Configuration
- [ ] Caching Strategy
- [ ] Sitemap Generation

---

*Dokument generiert am 2026-02-20*
