cms.c2sgmbh/prompts/community-phase1.md
Martin Porwoll 77f70876f4 chore: add Claude Code config, prompts, and tenant setup scripts
- Add .claude/ configuration (agents, commands, hooks, get-shit-done workflows)
- Add prompts/ directory with development planning documents
- Add scripts/setup-tenants/ with tenant configuration
- Add docs/screenshots/
- Remove obsolete phase2.2-corrections-report.md
- Update pnpm-lock.yaml
- Update detect-secrets.sh to ignore setup.sh (env var usage, not secrets)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 10:18:05 +00:00

60 KiB
Raw Blame History

Phase 1: Community Core Detailplan

Übersicht

Ziel: Community Management direkt im Hub mit YouTube API Integration und Vorbereitung für LinkedIn/Instagram/Facebook.

Voraussetzung: YouTube Operations Hub muss implementiert sein (YouTubeChannels, YouTubeContent Collections).

Kern-Features:

  1. Community Inbox (Unified View)
  2. YouTube Comments Sync (Direkte API)
  3. Response Templates mit Variablen
  4. Medical Flag System (CCS-spezifisch)
  5. AI-gestützte Sentiment-Analyse (Claude API)
  6. Multi-Platform Grundstruktur

Architektur Phase 1

┌─────────────────────────────────────────────────────────────────────────┐
│                         COMMUNITY CORE                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  ┌────────────────────────────────────────────────────────────────┐     │
│  │                    COMMUNITY INBOX VIEW                         │     │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐          │     │
│  │  │ YouTube  │ │ LinkedIn │ │Instagram │ │ Facebook │          │     │
│  │  │    ✓     │ │  (prep)  │ │  (prep)  │ │  (prep)  │          │     │
│  │  └──────────┘ └──────────┘ └──────────┘ └──────────┘          │     │
│  └────────────────────────────────────────────────────────────────┘     │
│                              │                                           │
│                              ▼                                           │
│  ┌────────────────────────────────────────────────────────────────┐     │
│  │                   INTEGRATION LAYER                             │     │
│  │                                                                  │     │
│  │  ┌─────────────────┐  ┌─────────────────┐  ┌────────────────┐  │     │
│  │  │  YouTube Data   │  │  Claude AI      │  │  Notification  │  │     │
│  │  │  API v3         │  │  (Sentiment)    │  │  Service       │  │     │
│  │  └─────────────────┘  └─────────────────┘  └────────────────┘  │     │
│  │                                                                  │     │
│  │  ┌─────────────────────────────────────────────────────────┐   │     │
│  │  │  Platform Adapters (Prepared for Phase 2)               │   │     │
│  │  │  • YouTubeAdapter ✓  • LinkedInAdapter (stub)           │   │     │
│  │  │  • InstagramAdapter (stub)  • FacebookAdapter (stub)    │   │     │
│  │  └─────────────────────────────────────────────────────────┘   │     │
│  └────────────────────────────────────────────────────────────────┘     │
│                                                                          │
│  ┌────────────────────────────────────────────────────────────────┐     │
│  │                    PAYLOAD COLLECTIONS                          │     │
│  │                                                                  │     │
│  │  • social-platforms        • community-interactions             │     │
│  │  • social-accounts         • community-templates                │     │
│  │  • community-rules                                              │     │
│  └────────────────────────────────────────────────────────────────┘     │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Teil 1: Access Control erweitern

1.0 Community Access Functions

// src/lib/communityAccess.ts

import type { Access } from 'payload'

interface UserWithRoles {
  id: number
  isSuperAdmin?: boolean
  is_super_admin?: boolean
  youtubeRole?: 'none' | 'viewer' | 'editor' | 'producer' | 'creator' | 'manager'
  youtube_role?: 'none' | 'viewer' | 'editor' | 'producer' | 'creator' | 'manager'
  communityRole?: 'none' | 'viewer' | 'moderator' | 'manager'
  community_role?: 'none' | 'viewer' | 'moderator' | 'manager'
}

const checkIsSuperAdmin = (user: UserWithRoles | null): boolean => {
  if (!user) return false
  return Boolean(user.isSuperAdmin || user.is_super_admin)
}

const getCommunityRole = (user: UserWithRoles | null): string | undefined => {
  if (!user) return undefined
  return user.communityRole || user.community_role
}

const getYouTubeRole = (user: UserWithRoles | null): string | undefined => {
  if (!user) return undefined
  return user.youtubeRole || user.youtube_role
}

/**
 * Prüft ob User Community-Manager oder Super-Admin ist
 */
export const isCommunityManager: Access = ({ req }) => {
  const user = req.user as UserWithRoles | null
  if (!user) return false
  if (checkIsSuperAdmin(user)) return true
  // YouTube-Manager haben auch Community-Zugriff
  if (getYouTubeRole(user) === 'manager') return true
  return getCommunityRole(user) === 'manager'
}

/**
 * Prüft ob User mindestens Moderator-Rechte hat
 */
export const isCommunityModeratorOrAbove: Access = ({ req }) => {
  const user = req.user as UserWithRoles | null
  if (!user) return false
  if (checkIsSuperAdmin(user)) return true
  if (['manager', 'creator'].includes(getYouTubeRole(user) || '')) return true
  return ['moderator', 'manager'].includes(getCommunityRole(user) || '')
}

/**
 * Prüft ob User Zugriff auf Community-Features hat (mindestens Viewer)
 */
export const hasCommunityAccess: Access = ({ req }) => {
  const user = req.user as UserWithRoles | null
  if (!user) return false
  if (checkIsSuperAdmin(user)) return true
  // YouTube-Zugriff impliziert Community-Lesezugriff
  const ytRole = getYouTubeRole(user)
  if (ytRole && ytRole !== 'none') return true
  const commRole = getCommunityRole(user)
  return commRole !== 'none' && commRole !== undefined
}

/**
 * Zugriff auf zugewiesene Interactions
 */
export const canAccessAssignedInteractions: Access = ({ req }) => {
  const user = req.user as UserWithRoles | null
  if (!user) return false
  if (checkIsSuperAdmin(user)) return true
  if (getYouTubeRole(user) === 'manager') return true
  if (getCommunityRole(user) === 'manager') return true

  // Für andere Rollen: Nur zugewiesene Interactions
  return {
    or: [
      { assignedTo: { equals: user.id } },
      { 'response.sentBy': { equals: user.id } },
    ],
  }
}

Teil 2: Collections

2.1 Social Platforms (social-platforms)

// src/collections/SocialPlatforms.ts

import type { CollectionConfig } from 'payload'
import { isCommunityManager, hasCommunityAccess } from '../lib/communityAccess'

export const SocialPlatforms: CollectionConfig = {
  slug: 'social-platforms',
  labels: {
    singular: 'Social Platform',
    plural: 'Social Platforms',
  },
  admin: {
    group: 'Community',
    useAsTitle: 'name',
    defaultColumns: ['name', 'slug', 'isActive', 'apiStatus'],
  },
  access: {
    read: hasCommunityAccess,
    create: isCommunityManager,
    update: isCommunityManager,
    delete: isCommunityManager,
  },
  fields: [
    {
      type: 'row',
      fields: [
        {
          name: 'name',
          type: 'text',
          required: true,
          label: 'Name',
          admin: { width: '50%' },
        },
        {
          name: 'slug',
          type: 'text',
          required: true,
          unique: true,
          label: 'Slug',
          admin: { width: '50%' },
        },
      ],
    },
    {
      type: 'row',
      fields: [
        {
          name: 'icon',
          type: 'text',
          label: 'Icon (Emoji)',
          admin: {
            width: '25%',
            placeholder: '📺',
          },
        },
        {
          name: 'color',
          type: 'text',
          label: 'Brand Color',
          admin: {
            width: '25%',
            placeholder: '#FF0000',
          },
        },
        {
          name: 'isActive',
          type: 'checkbox',
          label: 'Aktiv',
          defaultValue: true,
          admin: { width: '25%' },
        },
        {
          name: 'apiStatus',
          type: 'select',
          label: 'API Status',
          options: [
            { label: 'Verbunden', value: 'connected' },
            { label: 'Eingeschränkt', value: 'limited' },
            { label: 'Nicht verbunden', value: 'disconnected' },
            { label: 'In Entwicklung', value: 'development' },
          ],
          defaultValue: 'disconnected',
          admin: { width: '25%' },
        },
      ],
    },

    // API Configuration
    {
      name: 'apiConfig',
      type: 'group',
      label: 'API Konfiguration',
      admin: {
        condition: (data) => data?.isActive,
      },
      fields: [
        {
          name: 'apiType',
          type: 'select',
          label: 'API Type',
          options: [
            { label: 'YouTube Data API v3', value: 'youtube_v3' },
            { label: 'LinkedIn API', value: 'linkedin' },
            { label: 'Instagram Graph API', value: 'instagram_graph' },
            { label: 'Facebook Graph API', value: 'facebook_graph' },
            { label: 'Custom/Webhook', value: 'custom' },
          ],
        },
        {
          name: 'baseUrl',
          type: 'text',
          label: 'Base URL',
          admin: {
            placeholder: 'https://www.googleapis.com/youtube/v3',
          },
        },
        {
          name: 'authType',
          type: 'select',
          label: 'Auth Type',
          options: [
            { label: 'OAuth 2.0', value: 'oauth2' },
            { label: 'API Key', value: 'api_key' },
            { label: 'Bearer Token', value: 'bearer' },
          ],
        },
        {
          name: 'scopes',
          type: 'array',
          label: 'OAuth Scopes',
          admin: {
            condition: (data, siblingData) => siblingData?.authType === 'oauth2',
          },
          fields: [
            { name: 'scope', type: 'text', label: 'Scope' },
          ],
        },
      ],
    },

    // Interaction Types für diese Plattform
    {
      name: 'interactionTypes',
      type: 'array',
      label: 'Interaction Types',
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'type',
              type: 'text',
              required: true,
              label: 'Type',
              admin: {
                width: '30%',
                placeholder: 'comment',
              },
            },
            {
              name: 'label',
              type: 'text',
              required: true,
              label: 'Label',
              admin: {
                width: '30%',
                placeholder: 'Kommentar',
              },
            },
            {
              name: 'icon',
              type: 'text',
              label: 'Icon',
              admin: {
                width: '20%',
                placeholder: '💬',
              },
            },
            {
              name: 'canReply',
              type: 'checkbox',
              label: 'Reply möglich',
              defaultValue: true,
              admin: { width: '20%' },
            },
          ],
        },
      ],
    },

    // Rate Limits
    {
      name: 'rateLimits',
      type: 'group',
      label: 'Rate Limits',
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'requestsPerMinute',
              type: 'number',
              label: 'Requests/Minute',
              admin: { width: '33%' },
            },
            {
              name: 'requestsPerDay',
              type: 'number',
              label: 'Requests/Tag',
              admin: { width: '33%' },
            },
            {
              name: 'quotaUnitsPerDay',
              type: 'number',
              label: 'Quota Units/Tag',
              admin: {
                width: '33%',
                description: 'YouTube: 10.000/Tag',
              },
            },
          ],
        },
      ],
    },
  ],
  timestamps: true,
}

2.2 Social Accounts (social-accounts)

// src/collections/SocialAccounts.ts

import type { CollectionConfig } from 'payload'
import { isCommunityManager, hasCommunityAccess } from '../lib/communityAccess'

export const SocialAccounts: CollectionConfig = {
  slug: 'social-accounts',
  labels: {
    singular: 'Social Account',
    plural: 'Social Accounts',
  },
  admin: {
    group: 'Community',
    useAsTitle: 'displayName',
    defaultColumns: ['displayName', 'platform', 'linkedChannel', 'isActive'],
  },
  access: {
    read: hasCommunityAccess,
    create: isCommunityManager,
    update: isCommunityManager,
    delete: isCommunityManager,
  },
  fields: [
    {
      type: 'row',
      fields: [
        {
          name: 'platform',
          type: 'relationship',
          relationTo: 'social-platforms',
          required: true,
          label: 'Plattform',
          admin: { width: '50%' },
        },
        {
          name: 'linkedChannel',
          type: 'relationship',
          relationTo: 'youtube-channels',
          label: 'Verknüpfter YouTube-Kanal',
          admin: {
            width: '50%',
            description: 'Für Zuordnung zu Brand/Kanal',
          },
        },
      ],
    },
    {
      type: 'row',
      fields: [
        {
          name: 'displayName',
          type: 'text',
          required: true,
          label: 'Anzeigename',
          admin: {
            width: '50%',
            placeholder: 'BlogWoman YouTube',
          },
        },
        {
          name: 'accountHandle',
          type: 'text',
          label: 'Handle / Username',
          admin: {
            width: '50%',
            placeholder: '@blogwoman',
          },
        },
      ],
    },
    {
      type: 'row',
      fields: [
        {
          name: 'externalId',
          type: 'text',
          label: 'Platform Account ID',
          admin: {
            width: '50%',
            description: 'YouTube Channel ID, LinkedIn URN, etc.',
          },
        },
        {
          name: 'accountUrl',
          type: 'text',
          label: 'Account URL',
          admin: { width: '50%' },
        },
      ],
    },
    {
      name: 'isActive',
      type: 'checkbox',
      label: 'Aktiv',
      defaultValue: true,
      admin: {
        position: 'sidebar',
      },
    },

    // OAuth Credentials (verschlüsselt speichern!)
    {
      name: 'credentials',
      type: 'group',
      label: 'API Credentials',
      admin: {
        description: 'Sensible Daten  nur für Admins sichtbar',
        condition: (data, siblingData, { user }) =>
          Boolean((user as any)?.isSuperAdmin || (user as any)?.is_super_admin),
      },
      fields: [
        {
          name: 'accessToken',
          type: 'text',
          label: 'Access Token',
          admin: {
            description: 'OAuth Access Token',
          },
        },
        {
          name: 'refreshToken',
          type: 'text',
          label: 'Refresh Token',
        },
        {
          name: 'tokenExpiresAt',
          type: 'date',
          label: 'Token Ablauf',
        },
        {
          name: 'apiKey',
          type: 'text',
          label: 'API Key',
          admin: {
            description: 'Für API-Key basierte Auth',
          },
        },
      ],
    },

    // Stats (periodisch aktualisiert)
    {
      name: 'stats',
      type: 'group',
      label: 'Account Stats',
      admin: {
        position: 'sidebar',
      },
      fields: [
        {
          name: 'followers',
          type: 'number',
          label: 'Followers/Subscribers',
          admin: { readOnly: true },
        },
        {
          name: 'totalPosts',
          type: 'number',
          label: 'Total Posts/Videos',
          admin: { readOnly: true },
        },
        {
          name: 'lastSyncedAt',
          type: 'date',
          label: 'Letzter Sync',
          admin: { readOnly: true },
        },
      ],
    },

    // Sync Settings
    {
      name: 'syncSettings',
      type: 'group',
      label: 'Sync Settings',
      fields: [
        {
          name: 'autoSyncEnabled',
          type: 'checkbox',
          label: 'Auto-Sync aktiviert',
          defaultValue: true,
        },
        {
          name: 'syncIntervalMinutes',
          type: 'number',
          label: 'Sync-Intervall (Minuten)',
          defaultValue: 15,
          min: 5,
          max: 1440,
        },
        {
          name: 'syncComments',
          type: 'checkbox',
          label: 'Kommentare synchronisieren',
          defaultValue: true,
        },
        {
          name: 'syncDMs',
          type: 'checkbox',
          label: 'DMs synchronisieren',
          defaultValue: false,
          admin: {
            description: 'Nicht alle Plattformen unterstützen DM-API',
          },
        },
      ],
    },
  ],
  timestamps: true,
}

2.3 Community Interactions (community-interactions)

// src/collections/CommunityInteractions.ts

import type { CollectionConfig } from 'payload'
import {
  hasCommunityAccess,
  isCommunityModeratorOrAbove,
  isCommunityManager,
  canAccessAssignedInteractions
} from '../lib/communityAccess'

export const CommunityInteractions: CollectionConfig = {
  slug: 'community-interactions',
  labels: {
    singular: 'Interaction',
    plural: 'Interactions',
  },
  admin: {
    group: 'Community',
    defaultColumns: ['platform', 'type', 'authorName', 'status', 'priority', 'createdAt'],
    listSearchableFields: ['author.name', 'author.handle', 'message'],
    pagination: {
      defaultLimit: 50,
    },
  },
  access: {
    read: canAccessAssignedInteractions,
    create: isCommunityModeratorOrAbove,
    update: canAccessAssignedInteractions,
    delete: isCommunityManager,
  },
  fields: [
    // === SOURCE ===
    {
      type: 'row',
      fields: [
        {
          name: 'platform',
          type: 'relationship',
          relationTo: 'social-platforms',
          required: true,
          label: 'Plattform',
          admin: { width: '33%' },
        },
        {
          name: 'socialAccount',
          type: 'relationship',
          relationTo: 'social-accounts',
          required: true,
          label: 'Account',
          admin: { width: '33%' },
        },
        {
          name: 'linkedContent',
          type: 'relationship',
          relationTo: 'youtube-content',
          label: 'Verknüpfter Content',
          admin: { width: '33%' },
        },
      ],
    },

    // === INTERACTION TYPE ===
    {
      type: 'row',
      fields: [
        {
          name: 'type',
          type: 'select',
          required: true,
          label: 'Typ',
          options: [
            { label: 'Kommentar', value: 'comment' },
            { label: 'Antwort', value: 'reply' },
            { label: 'Direktnachricht', value: 'dm' },
            { label: 'Erwähnung', value: 'mention' },
            { label: 'Bewertung', value: 'review' },
            { label: 'Frage', value: 'question' },
          ],
          admin: { width: '50%' },
        },
        {
          name: 'externalId',
          type: 'text',
          label: 'External ID',
          required: true,
          unique: true,
          index: true,
          admin: {
            width: '50%',
            description: 'YouTube Comment ID, etc.',
          },
        },
      ],
    },

    // === PARENT (für Threads) ===
    {
      name: 'parentInteraction',
      type: 'relationship',
      relationTo: 'community-interactions',
      label: 'Parent (bei Replies)',
      admin: {
        condition: (data) => data?.type === 'reply',
      },
    },

    // === AUTHOR INFO ===
    {
      name: 'author',
      type: 'group',
      label: 'Author',
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'name',
              type: 'text',
              label: 'Name',
              admin: { width: '50%' },
            },
            {
              name: 'handle',
              type: 'text',
              label: 'Handle',
              admin: { width: '50%' },
            },
          ],
        },
        {
          type: 'row',
          fields: [
            {
              name: 'profileUrl',
              type: 'text',
              label: 'Profile URL',
              admin: { width: '50%' },
            },
            {
              name: 'avatarUrl',
              type: 'text',
              label: 'Avatar URL',
              admin: { width: '50%' },
            },
          ],
        },
        {
          type: 'row',
          fields: [
            {
              name: 'isVerified',
              type: 'checkbox',
              label: 'Verifiziert',
              admin: { width: '25%' },
            },
            {
              name: 'isSubscriber',
              type: 'checkbox',
              label: 'Subscriber/Follower',
              admin: { width: '25%' },
            },
            {
              name: 'isMember',
              type: 'checkbox',
              label: 'Channel Member',
              admin: { width: '25%' },
            },
            {
              name: 'subscriberCount',
              type: 'number',
              label: 'Ihre Subscriber',
              admin: { width: '25%' },
            },
          ],
        },
      ],
    },

    // === MESSAGE CONTENT ===
    {
      name: 'message',
      type: 'textarea',
      label: 'Nachricht',
      required: true,
      admin: {
        rows: 4,
      },
    },
    {
      name: 'messageHtml',
      type: 'textarea',
      label: 'Original HTML',
      admin: {
        rows: 2,
        description: 'Falls Plattform HTML liefert',
      },
    },
    {
      name: 'attachments',
      type: 'array',
      label: 'Attachments',
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'type',
              type: 'select',
              label: 'Typ',
              options: [
                { label: 'Bild', value: 'image' },
                { label: 'Video', value: 'video' },
                { label: 'Link', value: 'link' },
                { label: 'Sticker', value: 'sticker' },
              ],
              admin: { width: '30%' },
            },
            {
              name: 'url',
              type: 'text',
              label: 'URL',
              admin: { width: '70%' },
            },
          ],
        },
      ],
    },
    {
      name: 'publishedAt',
      type: 'date',
      label: 'Veröffentlicht am',
      required: true,
      admin: {
        date: {
          pickerAppearance: 'dayAndTime',
        },
      },
    },

    // === AI ANALYSIS ===
    {
      name: 'analysis',
      type: 'group',
      label: 'AI Analyse',
      admin: {
        description: 'Automatisch via Claude API',
      },
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'sentiment',
              type: 'select',
              label: 'Sentiment',
              options: [
                { label: 'Positiv', value: 'positive' },
                { label: 'Neutral', value: 'neutral' },
                { label: 'Negativ', value: 'negative' },
                { label: 'Frage', value: 'question' },
                { label: 'Dankbarkeit', value: 'gratitude' },
                { label: 'Frustration', value: 'frustration' },
              ],
              admin: { width: '33%' },
            },
            {
              name: 'sentimentScore',
              type: 'number',
              label: 'Score (-1 bis 1)',
              min: -1,
              max: 1,
              admin: { width: '33%' },
            },
            {
              name: 'confidence',
              type: 'number',
              label: 'Confidence %',
              min: 0,
              max: 100,
              admin: { width: '33%' },
            },
          ],
        },
        {
          name: 'topics',
          type: 'array',
          label: 'Erkannte Themen',
          fields: [
            {
              name: 'topic',
              type: 'text',
              label: 'Thema',
            },
          ],
        },
        {
          name: 'language',
          type: 'text',
          label: 'Sprache',
          admin: {
            placeholder: 'de',
          },
        },
        {
          name: 'suggestedTemplate',
          type: 'relationship',
          relationTo: 'community-templates',
          label: 'Vorgeschlagenes Template',
        },
        {
          name: 'suggestedReply',
          type: 'textarea',
          label: 'AI-generierter Antwortvorschlag',
        },
        {
          name: 'analyzedAt',
          type: 'date',
          label: 'Analysiert am',
        },
      ],
    },

    // === FLAGS ===
    {
      name: 'flags',
      type: 'group',
      label: 'Flags',
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'isMedicalQuestion',
              type: 'checkbox',
              label: 'Medizinische Frage',
              admin: {
                width: '25%',
                description: 'Erfordert ärztliche Review',
              },
            },
            {
              name: 'requiresEscalation',
              type: 'checkbox',
              label: 'Eskalation nötig',
              admin: { width: '25%' },
            },
            {
              name: 'isSpam',
              type: 'checkbox',
              label: 'Spam',
              admin: { width: '25%' },
            },
            {
              name: 'isFromInfluencer',
              type: 'checkbox',
              label: 'Influencer',
              admin: {
                width: '25%',
                description: '>10k Follower',
              },
            },
          ],
        },
      ],
    },

    // === WORKFLOW ===
    {
      name: 'status',
      type: 'select',
      required: true,
      defaultValue: 'new',
      index: true,
      label: 'Status',
      options: [
        { label: 'Neu', value: 'new' },
        { label: 'In Review', value: 'in_review' },
        { label: 'Warten auf Info', value: 'waiting' },
        { label: 'Beantwortet', value: 'replied' },
        { label: 'Erledigt', value: 'resolved' },
        { label: 'Archiviert', value: 'archived' },
        { label: 'Spam', value: 'spam' },
      ],
      admin: {
        position: 'sidebar',
      },
    },
    {
      name: 'priority',
      type: 'select',
      required: true,
      defaultValue: 'normal',
      index: true,
      label: 'Priorität',
      options: [
        { label: 'Urgent', value: 'urgent' },
        { label: 'Hoch', value: 'high' },
        { label: 'Normal', value: 'normal' },
        { label: 'Niedrig', value: 'low' },
      ],
      admin: {
        position: 'sidebar',
      },
    },
    {
      name: 'assignedTo',
      type: 'relationship',
      relationTo: 'users',
      label: 'Zugewiesen an',
      admin: {
        position: 'sidebar',
      },
    },
    {
      name: 'responseDeadline',
      type: 'date',
      label: 'Antwort-Deadline',
      admin: {
        position: 'sidebar',
        date: {
          pickerAppearance: 'dayAndTime',
        },
      },
    },

    // === OUR RESPONSE ===
    {
      name: 'response',
      type: 'group',
      label: 'Unsere Antwort',
      fields: [
        {
          name: 'text',
          type: 'textarea',
          label: 'Antwort-Text',
          admin: { rows: 4 },
        },
        {
          name: 'usedTemplate',
          type: 'relationship',
          relationTo: 'community-templates',
          label: 'Verwendetes Template',
        },
        {
          name: 'sentAt',
          type: 'date',
          label: 'Gesendet am',
        },
        {
          name: 'sentBy',
          type: 'relationship',
          relationTo: 'users',
          label: 'Gesendet von',
        },
        {
          name: 'externalReplyId',
          type: 'text',
          label: 'Reply ID (extern)',
        },
      ],
    },

    // === ENGAGEMENT (Platform-spezifisch) ===
    {
      name: 'engagement',
      type: 'group',
      label: 'Engagement',
      admin: {
        description: 'Wird beim Sync aktualisiert',
      },
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'likes',
              type: 'number',
              label: 'Likes',
              admin: { width: '25%' },
            },
            {
              name: 'replies',
              type: 'number',
              label: 'Replies',
              admin: { width: '25%' },
            },
            {
              name: 'isHearted',
              type: 'checkbox',
              label: 'Creator Heart',
              admin: { width: '25%' },
            },
            {
              name: 'isPinned',
              type: 'checkbox',
              label: 'Angepinnt',
              admin: { width: '25%' },
            },
          ],
        },
      ],
    },

    // === INTERNAL NOTES ===
    {
      name: 'internalNotes',
      type: 'textarea',
      label: 'Interne Notizen',
      admin: {
        rows: 2,
        description: 'Nur für Team sichtbar',
      },
    },
  ],

  // === HOOKS ===
  hooks: {
    beforeChange: [
      // Auto-set priority based on flags
      async ({ data, operation }) => {
        if (!data) return data
        if (operation === 'create' || !data.priority) {
          if (data?.flags?.isMedicalQuestion) {
            data.priority = 'high'
          }
          if (data?.flags?.requiresEscalation) {
            data.priority = 'urgent'
          }
          if (data?.flags?.isFromInfluencer) {
            data.priority = data.priority === 'urgent' ? 'urgent' : 'high'
          }
        }
        return data
      },
    ],
    afterChange: [
      // Send notification for urgent items
      async ({ doc, operation }) => {
        if (operation === 'create' && doc.priority === 'urgent') {
          // TODO: Notification Logic
          console.log(`🚨 Urgent interaction: ${doc.id}`)
        }
      },
    ],
  },
  timestamps: true,
}

2.4 Community Templates (community-templates)

// src/collections/CommunityTemplates.ts

import type { CollectionConfig } from 'payload'
import { isCommunityManager, isCommunityModeratorOrAbove, hasCommunityAccess } from '../lib/communityAccess'

export const CommunityTemplates: CollectionConfig = {
  slug: 'community-templates',
  labels: {
    singular: 'Response Template',
    plural: 'Response Templates',
  },
  admin: {
    group: 'Community',
    useAsTitle: 'name',
    defaultColumns: ['name', 'category', 'channel', 'usageCount'],
  },
  access: {
    read: hasCommunityAccess,
    create: isCommunityModeratorOrAbove,
    update: isCommunityModeratorOrAbove,
    delete: isCommunityManager,
  },
  fields: [
    {
      type: 'row',
      fields: [
        {
          name: 'name',
          type: 'text',
          required: true,
          localized: true,
          label: 'Name',
          admin: { width: '50%' },
        },
        {
          name: 'category',
          type: 'select',
          required: true,
          label: 'Kategorie',
          options: [
            { label: 'Danke', value: 'thank_you' },
            { label: 'Frage beantworten', value: 'question_answer' },
            { label: 'Hotline-Verweis', value: 'redirect_hotline' },
            { label: 'Medizinischer Disclaimer', value: 'medical_disclaimer' },
            { label: 'Produkt-Info', value: 'product_info' },
            { label: 'Content-Verweis', value: 'content_reference' },
            { label: 'Follow-up', value: 'follow_up' },
            { label: 'Negatives Feedback', value: 'negative_feedback' },
            { label: 'Spam-Antwort', value: 'spam_response' },
            { label: 'Begrüßung', value: 'welcome' },
          ],
          admin: { width: '50%' },
        },
      ],
    },
    {
      type: 'row',
      fields: [
        {
          name: 'channel',
          type: 'relationship',
          relationTo: 'youtube-channels',
          label: 'Kanal (optional)',
          admin: {
            width: '50%',
            description: 'Leer = für alle Kanäle',
          },
        },
        {
          name: 'platforms',
          type: 'relationship',
          relationTo: 'social-platforms',
          hasMany: true,
          label: 'Plattformen',
          admin: {
            width: '50%',
            description: 'Leer = für alle Plattformen',
          },
        },
      ],
    },

    // Template Text mit Variablen
    {
      name: 'template',
      type: 'textarea',
      required: true,
      localized: true,
      label: 'Template Text',
      admin: {
        rows: 6,
        description: 'Variablen: {{author_name}}, {{video_title}}, {{channel_name}}, {{hotline_number}}',
      },
    },

    // Verfügbare Variablen
    {
      name: 'variables',
      type: 'array',
      label: 'Verfügbare Variablen',
      admin: {
        description: 'Dokumentation der Variablen in diesem Template',
      },
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'variable',
              type: 'text',
              required: true,
              label: 'Variable',
              admin: {
                width: '30%',
                placeholder: '{{author_name}}',
              },
            },
            {
              name: 'description',
              type: 'text',
              label: 'Beschreibung',
              admin: {
                width: '50%',
                placeholder: 'Name des Kommentar-Autors',
              },
            },
            {
              name: 'defaultValue',
              type: 'text',
              label: 'Fallback',
              admin: {
                width: '20%',
              },
            },
          ],
        },
      ],
    },

    // Auto-Suggest Keywords
    {
      name: 'autoSuggestKeywords',
      type: 'array',
      label: 'Auto-Suggest Keywords',
      admin: {
        description: 'Bei diesen Keywords wird das Template vorgeschlagen',
      },
      fields: [
        {
          name: 'keyword',
          type: 'text',
          required: true,
          label: 'Keyword',
        },
      ],
    },

    // Flags
    {
      type: 'row',
      fields: [
        {
          name: 'requiresReview',
          type: 'checkbox',
          label: 'Review erforderlich',
          admin: {
            width: '33%',
            description: 'Für medizinische Antworten',
          },
        },
        {
          name: 'isActive',
          type: 'checkbox',
          label: 'Aktiv',
          defaultValue: true,
          admin: { width: '33%' },
        },
        {
          name: 'usageCount',
          type: 'number',
          label: 'Verwendungen',
          defaultValue: 0,
          admin: {
            width: '33%',
            readOnly: true,
          },
        },
      ],
    },

    // Beispiel-Output
    {
      name: 'exampleOutput',
      type: 'textarea',
      label: 'Beispiel-Output',
      admin: {
        rows: 3,
        description: 'So sieht die Antwort mit ausgefüllten Variablen aus',
      },
    },
  ],
  timestamps: true,
}

2.5 Community Rules (community-rules)

// src/collections/CommunityRules.ts

import type { CollectionConfig } from 'payload'
import { isCommunityManager, hasCommunityAccess } from '../lib/communityAccess'

export const CommunityRules: CollectionConfig = {
  slug: 'community-rules',
  labels: {
    singular: 'Community Rule',
    plural: 'Community Rules',
  },
  admin: {
    group: 'Community',
    useAsTitle: 'name',
    defaultColumns: ['name', 'trigger.type', 'isActive', 'priority'],
  },
  access: {
    read: hasCommunityAccess,
    create: isCommunityManager,
    update: isCommunityManager,
    delete: isCommunityManager,
  },
  fields: [
    {
      type: 'row',
      fields: [
        {
          name: 'name',
          type: 'text',
          required: true,
          label: 'Name',
          admin: { width: '50%' },
        },
        {
          name: 'priority',
          type: 'number',
          required: true,
          defaultValue: 100,
          label: 'Priorität',
          admin: {
            width: '25%',
            description: 'Niedrigere Zahl = höhere Priorität',
          },
        },
        {
          name: 'isActive',
          type: 'checkbox',
          label: 'Aktiv',
          defaultValue: true,
          admin: { width: '25%' },
        },
      ],
    },
    {
      name: 'description',
      type: 'textarea',
      label: 'Beschreibung',
      admin: { rows: 2 },
    },

    // Scope
    {
      type: 'row',
      fields: [
        {
          name: 'channel',
          type: 'relationship',
          relationTo: 'youtube-channels',
          label: 'Kanal (optional)',
          admin: {
            width: '50%',
            description: 'Leer = alle Kanäle',
          },
        },
        {
          name: 'platforms',
          type: 'relationship',
          relationTo: 'social-platforms',
          hasMany: true,
          label: 'Plattformen',
          admin: {
            width: '50%',
            description: 'Leer = alle Plattformen',
          },
        },
      ],
    },

    // Trigger
    {
      name: 'trigger',
      type: 'group',
      label: 'Trigger',
      fields: [
        {
          name: 'type',
          type: 'select',
          required: true,
          label: 'Trigger-Typ',
          options: [
            { label: 'Keyword Match', value: 'keyword' },
            { label: 'Sentiment', value: 'sentiment' },
            { label: 'Frage erkannt', value: 'question_detected' },
            { label: 'Medizinisch erkannt', value: 'medical_detected' },
            { label: 'Influencer', value: 'influencer' },
            { label: 'Alle neuen', value: 'all_new' },
            { label: 'Enthält Link', value: 'contains_link' },
            { label: 'Enthält Email', value: 'contains_email' },
          ],
        },
        {
          name: 'keywords',
          type: 'array',
          label: 'Keywords',
          admin: {
            condition: (data, siblingData) => siblingData?.type === 'keyword',
          },
          fields: [
            {
              name: 'keyword',
              type: 'text',
              required: true,
              label: 'Keyword',
            },
            {
              name: 'matchType',
              type: 'select',
              label: 'Match-Typ',
              options: [
                { label: 'Enthält', value: 'contains' },
                { label: 'Exakt', value: 'exact' },
                { label: 'Regex', value: 'regex' },
              ],
              defaultValue: 'contains',
            },
          ],
        },
        {
          name: 'sentimentValues',
          type: 'select',
          hasMany: true,
          label: 'Sentiment-Werte',
          options: [
            { label: 'Positiv', value: 'positive' },
            { label: 'Negativ', value: 'negative' },
            { label: 'Neutral', value: 'neutral' },
            { label: 'Frage', value: 'question' },
          ],
          admin: {
            condition: (data, siblingData) => siblingData?.type === 'sentiment',
          },
        },
        {
          name: 'influencerMinFollowers',
          type: 'number',
          label: 'Min. Follower',
          defaultValue: 10000,
          admin: {
            condition: (data, siblingData) => siblingData?.type === 'influencer',
          },
        },
      ],
    },

    // Actions
    {
      name: 'actions',
      type: 'array',
      label: 'Aktionen',
      required: true,
      minRows: 1,
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'action',
              type: 'select',
              required: true,
              label: 'Aktion',
              options: [
                { label: 'Priorität setzen', value: 'set_priority' },
                { label: 'Zuweisen', value: 'assign_to' },
                { label: 'Flag setzen', value: 'set_flag' },
                { label: 'Template vorschlagen', value: 'suggest_template' },
                { label: 'Notification senden', value: 'send_notification' },
                { label: 'Medical Flag', value: 'flag_medical' },
                { label: 'Eskalieren', value: 'escalate' },
                { label: 'Als Spam markieren', value: 'mark_spam' },
                { label: 'Deadline setzen', value: 'set_deadline' },
              ],
              admin: { width: '40%' },
            },
            {
              name: 'value',
              type: 'text',
              label: 'Wert',
              admin: {
                width: '40%',
                description: 'Priority: urgent/high/normal/low, Deadline: Stunden',
              },
            },
            {
              name: 'targetUser',
              type: 'relationship',
              relationTo: 'users',
              label: 'User',
              admin: {
                width: '20%',
                condition: (data, siblingData) =>
                  ['assign_to', 'send_notification'].includes(siblingData?.action || ''),
              },
            },
          ],
        },
        {
          name: 'targetTemplate',
          type: 'relationship',
          relationTo: 'community-templates',
          label: 'Template',
          admin: {
            condition: (data, siblingData) => siblingData?.action === 'suggest_template',
          },
        },
      ],
    },

    // Stats
    {
      name: 'stats',
      type: 'group',
      label: 'Statistiken',
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'timesTriggered',
              type: 'number',
              label: 'Ausgelöst',
              defaultValue: 0,
              admin: { width: '50%', readOnly: true },
            },
            {
              name: 'lastTriggeredAt',
              type: 'date',
              label: 'Zuletzt ausgelöst',
              admin: { width: '50%', readOnly: true },
            },
          ],
        },
      ],
    },
  ],
  timestamps: true,
}

Teil 3: Users Collection erweitern

3.1 Community-Rolle zu Users hinzufügen

// Ergänzung in src/collections/Users.ts (zu den bestehenden YouTube-Feldern)

{
  name: 'communityRole',
  type: 'select',
  label: 'Community-Rolle',
  defaultValue: 'none',
  options: [
    { label: 'Keine', value: 'none' },
    { label: 'Viewer', value: 'viewer' },
    { label: 'Moderator', value: 'moderator' },
    { label: 'Manager', value: 'manager' },
  ],
  admin: {
    position: 'sidebar',
    description: 'Zugriff auf Community Management Features',
  },
},

Teil 4: YouTube API Integration

4.1 YouTube API Client

// src/lib/integrations/youtube/YouTubeClient.ts

import { google, youtube_v3 } from 'googleapis'
import type { Payload } from 'payload'

interface YouTubeCredentials {
  clientId: string
  clientSecret: string
  accessToken: string
  refreshToken: string
}

interface CommentThread {
  id: string
  snippet: {
    videoId: string
    topLevelComment: {
      id: string
      snippet: {
        textDisplay: string
        textOriginal: string
        authorDisplayName: string
        authorProfileImageUrl: string
        authorChannelUrl: string
        authorChannelId: { value: string }
        likeCount: number
        publishedAt: string
        updatedAt: string
      }
    }
    totalReplyCount: number
  }
}

export class YouTubeClient {
  private youtube: youtube_v3.Youtube
  private oauth2Client: any
  private payload: Payload

  constructor(credentials: YouTubeCredentials, payload: Payload) {
    this.payload = payload

    this.oauth2Client = new google.auth.OAuth2(
      credentials.clientId,
      credentials.clientSecret,
      process.env.YOUTUBE_REDIRECT_URI
    )

    this.oauth2Client.setCredentials({
      access_token: credentials.accessToken,
      refresh_token: credentials.refreshToken,
    })

    this.youtube = google.youtube({
      version: 'v3',
      auth: this.oauth2Client,
    })
  }

  /**
   * Alle Kommentar-Threads eines Videos abrufen
   */
  async getVideoComments(
    videoId: string,
    pageToken?: string,
    maxResults: number = 100
  ): Promise<{
    comments: CommentThread[]
    nextPageToken?: string
  }> {
    try {
      const response = await this.youtube.commentThreads.list({
        part: ['snippet', 'replies'],
        videoId: videoId,
        maxResults: maxResults,
        pageToken: pageToken,
        order: 'time',
        textFormat: 'plainText',
      })

      return {
        comments: response.data.items as CommentThread[],
        nextPageToken: response.data.nextPageToken || undefined,
      }
    } catch (error) {
      console.error('Error fetching comments:', error)
      throw error
    }
  }

  /**
   * Alle Kommentare eines Kanals abrufen (alle Videos)
   */
  async getChannelComments(
    channelId: string,
    publishedAfter?: Date,
    maxResults: number = 100
  ): Promise<CommentThread[]> {
    try {
      const params: any = {
        part: ['snippet', 'replies'],
        allThreadsRelatedToChannelId: channelId,
        maxResults: maxResults,
        order: 'time',
        textFormat: 'plainText',
      }

      if (publishedAfter) {
        params.publishedAfter = publishedAfter.toISOString()
      }

      const response = await this.youtube.commentThreads.list(params)
      return response.data.items as CommentThread[]
    } catch (error) {
      console.error('Error fetching channel comments:', error)
      throw error
    }
  }

  /**
   * Auf einen Kommentar antworten
   */
  async replyToComment(parentCommentId: string, text: string): Promise<string> {
    try {
      const response = await this.youtube.comments.insert({
        part: ['snippet'],
        requestBody: {
          snippet: {
            parentId: parentCommentId,
            textOriginal: text,
          },
        },
      })

      return response.data.id!
    } catch (error) {
      console.error('Error replying to comment:', error)
      throw error
    }
  }

  /**
   * Kommentar als Spam markieren
   */
  async markAsSpam(commentId: string): Promise<void> {
    try {
      await this.youtube.comments.setModerationStatus({
        id: [commentId],
        moderationStatus: 'rejected',
      })
    } catch (error) {
      console.error('Error marking as spam:', error)
      throw error
    }
  }

  /**
   * Kommentar löschen
   */
  async deleteComment(commentId: string): Promise<void> {
    try {
      await this.youtube.comments.delete({
        id: commentId,
      })
    } catch (error) {
      console.error('Error deleting comment:', error)
      throw error
    }
  }

  /**
   * Access Token erneuern
   */
  async refreshAccessToken(): Promise<{
    accessToken: string
    expiresAt: Date
  }> {
    try {
      const { credentials } = await this.oauth2Client.refreshAccessToken()

      return {
        accessToken: credentials.access_token!,
        expiresAt: new Date(credentials.expiry_date!),
      }
    } catch (error) {
      console.error('Error refreshing token:', error)
      throw error
    }
  }
}

4.2 Claude Analysis Service

// src/lib/integrations/claude/ClaudeAnalysisService.ts

import Anthropic from '@anthropic-ai/sdk'

interface CommentAnalysis {
  sentiment: 'positive' | 'neutral' | 'negative' | 'question' | 'gratitude' | 'frustration'
  sentimentScore: number
  confidence: number
  topics: string[]
  language: string
  isMedicalQuestion: boolean
  requiresEscalation: boolean
  isSpam: boolean
  suggestedReply?: string
}

export class ClaudeAnalysisService {
  private client: Anthropic

  constructor() {
    this.client = new Anthropic({
      apiKey: process.env.ANTHROPIC_API_KEY,
    })
  }

  /**
   * Kommentar analysieren
   */
  async analyzeComment(message: string): Promise<CommentAnalysis> {
    const systemPrompt = `Du bist ein Analyse-Assistent für YouTube-Kommentare eines deutschen Healthcare-Unternehmens (Medizinische Zweitmeinung) und eines Lifestyle-Kanals.

Analysiere den Kommentar und gib ein JSON-Objekt zurück mit:
- sentiment: "positive", "neutral", "negative", "question", "gratitude", "frustration"
- sentimentScore: Zahl von -1 (sehr negativ) bis 1 (sehr positiv)
- confidence: Konfidenz der Analyse 0-100
- topics: Array von erkannten Themen (max 3)
- language: ISO-639-1 Sprachcode (z.B. "de", "en")
- isMedicalQuestion: true wenn es um medizinische Fragen/Gesundheit geht
- requiresEscalation: true wenn dringend/kritisch/negativ oder Beschwerden
- isSpam: true wenn Spam/Werbung/Bot
- suggestedReply: Kurzer Antwortvorschlag auf Deutsch (optional, nur wenn sinnvoll)

WICHTIG für isMedicalQuestion:
- Fragen zu Intensivmedizin, Diagnosen, Behandlungen, Medikamenten = true
- Fragen zu Angehörigen von Patienten = true
- Allgemeine Lifestyle-Fragen (Mode, Zeitmanagement) = false

Antworte NUR mit dem JSON-Objekt, kein anderer Text.`

    try {
      const response = await this.client.messages.create({
        model: 'claude-3-haiku-20240307',
        max_tokens: 500,
        system: systemPrompt,
        messages: [
          {
            role: 'user',
            content: `Analysiere diesen Kommentar:\n\n"${message}"`,
          },
        ],
      })

      const content = response.content[0]
      if (content.type !== 'text') {
        throw new Error('Unexpected response type')
      }

      const analysis = JSON.parse(content.text) as CommentAnalysis
      return analysis
    } catch (error) {
      console.error('Claude analysis error:', error)

      // Fallback bei Fehler
      return {
        sentiment: 'neutral',
        sentimentScore: 0,
        confidence: 0,
        topics: [],
        language: 'de',
        isMedicalQuestion: false,
        requiresEscalation: false,
        isSpam: false,
      }
    }
  }

  /**
   * Antwort-Vorschlag generieren
   */
  async generateReply(
    comment: string,
    context: {
      videoTitle: string
      channelName: string
      isBusinessChannel: boolean
      template?: string
    }
  ): Promise<string> {
    const systemPrompt = `Du bist ein Community-Manager für ${context.channelName}.
${context.isBusinessChannel
  ? 'Dies ist ein Healthcare-Kanal für medizinische Zweitmeinungen. Antworten müssen professionell sein und dürfen keine medizinischen Ratschläge geben. Bei medizinischen Fragen immer auf die Hotline verweisen.'
  : 'Dies ist ein Lifestyle-Kanal für berufstätige Mütter. Antworten sollten warm, persönlich und hilfreich sein.'
}

Erstelle eine passende, kurze Antwort auf den Kommentar.
- Maximal 2-3 Sätze
- Persönlich und authentisch
- Auf Deutsch
${context.template ? `\nVerwende dieses Template als Basis:\n${context.template}` : ''}

Antworte NUR mit dem Antworttext, kein anderer Text.`

    try {
      const response = await this.client.messages.create({
        model: 'claude-3-haiku-20240307',
        max_tokens: 200,
        system: systemPrompt,
        messages: [
          {
            role: 'user',
            content: `Video: "${context.videoTitle}"\n\nKommentar: "${comment}"\n\nErstelle eine Antwort:`,
          },
        ],
      })

      const content = response.content[0]
      if (content.type !== 'text') {
        throw new Error('Unexpected response type')
      }

      return content.text.trim()
    } catch (error) {
      console.error('Claude reply generation error:', error)
      throw error
    }
  }
}

Teil 5: API Endpoints

5.1 Comments Sync Endpoint

// src/app/(payload)/api/community/sync-comments/route.ts

import { NextRequest, NextResponse } from 'next/server'
import { getPayload } from 'payload'
import config from '@payload-config'
import { CommentsSyncService } from '@/lib/integrations/youtube/CommentsSyncService'

export async function POST(req: NextRequest) {
  try {
    const payload = await getPayload({ config })
    const body = await req.json()

    const { socialAccountId, sinceDate, maxComments, analyzeWithAI } = body

    if (!socialAccountId) {
      return NextResponse.json(
        { error: 'socialAccountId required' },
        { status: 400 }
      )
    }

    const syncService = new CommentsSyncService(payload)

    const result = await syncService.syncComments({
      socialAccountId,
      sinceDate: sinceDate ? new Date(sinceDate) : undefined,
      maxComments: maxComments || 100,
      analyzeWithAI: analyzeWithAI ?? true,
    })

    return NextResponse.json(result)
  } catch (error: any) {
    console.error('Sync error:', error)
    return NextResponse.json(
      { error: error.message },
      { status: 500 }
    )
  }
}

5.2 Reply Endpoint

// src/app/(payload)/api/community/reply/route.ts

import { NextRequest, NextResponse } from 'next/server'
import { getPayload } from 'payload'
import config from '@payload-config'
import { YouTubeClient } from '@/lib/integrations/youtube/YouTubeClient'

export async function POST(req: NextRequest) {
  try {
    const payload = await getPayload({ config })
    const body = await req.json()

    const { interactionId, replyText, templateId } = body

    if (!interactionId || !replyText) {
      return NextResponse.json(
        { error: 'interactionId and replyText required' },
        { status: 400 }
      )
    }

    // 1. Interaction laden
    const interaction = await payload.findByID({
      collection: 'community-interactions',
      id: interactionId,
      depth: 2,
    })

    if (!interaction) {
      return NextResponse.json(
        { error: 'Interaction not found' },
        { status: 404 }
      )
    }

    // 2. YouTube Client initialisieren
    const account = interaction.socialAccount as any
    const youtubeClient = new YouTubeClient({
      clientId: process.env.YOUTUBE_CLIENT_ID!,
      clientSecret: process.env.YOUTUBE_CLIENT_SECRET!,
      accessToken: account.credentials.accessToken,
      refreshToken: account.credentials.refreshToken,
    }, payload)

    // 3. Reply senden
    const replyId = await youtubeClient.replyToComment(
      interaction.externalId,
      replyText
    )

    // 4. Interaction aktualisieren
    await payload.update({
      collection: 'community-interactions',
      id: interactionId,
      data: {
        status: 'replied',
        response: {
          text: replyText,
          usedTemplate: templateId || null,
          sentAt: new Date().toISOString(),
          externalReplyId: replyId,
        },
      },
    })

    // 5. Template Usage Counter erhöhen
    if (templateId) {
      const template = await payload.findByID({
        collection: 'community-templates',
        id: templateId,
      })
      await payload.update({
        collection: 'community-templates',
        id: templateId,
        data: {
          usageCount: ((template as any).usageCount || 0) + 1,
        },
      })
    }

    return NextResponse.json({
      success: true,
      replyId,
    })
  } catch (error: any) {
    console.error('Reply error:', error)
    return NextResponse.json(
      { error: error.message },
      { status: 500 }
    )
  }
}

Teil 6: Environment Variables

# .env - Neue Variablen für Community Phase 1

# YouTube API (Google Cloud Console)
YOUTUBE_CLIENT_ID=your-client-id.apps.googleusercontent.com
YOUTUBE_CLIENT_SECRET=your-client-secret
YOUTUBE_REDIRECT_URI=https://pl.porwoll.tech/api/youtube/callback

# Claude API (Anthropic) - bereits vorhanden für andere Features
ANTHROPIC_API_KEY=sk-ant-api03-...

# Optional: Für spätere Phasen vorbereitet
LINKEDIN_CLIENT_ID=
LINKEDIN_CLIENT_SECRET=
INSTAGRAM_ACCESS_TOKEN=
FACEBOOK_ACCESS_TOKEN=

Teil 7: payload.config.ts Ergänzungen

// payload.config.ts - Ergänzungen für Community Phase 1

import { SocialPlatforms } from './collections/SocialPlatforms'
import { SocialAccounts } from './collections/SocialAccounts'
import { CommunityInteractions } from './collections/CommunityInteractions'
import { CommunityTemplates } from './collections/CommunityTemplates'
import { CommunityRules } from './collections/CommunityRules'

export default buildConfig({
  // ...
  collections: [
    // Existing YouTube Collections
    YouTubeChannels,
    YouTubeContent,
    YtSeries,
    YtTasks,
    YtNotifications,
    YtBatches,
    YtMonthlyGoals,
    YtScriptTemplates,
    YtChecklistTemplates,

    // NEW: Community Collections
    SocialPlatforms,
    SocialAccounts,
    CommunityInteractions,
    CommunityTemplates,
    CommunityRules,
  ],
  // ...
})

Teil 8: Migration

Die Migration muss folgende Tabellen erstellen:

  • social_platforms + social_platforms_interaction_types + social_platforms_api_config_scopes
  • social_accounts
  • community_interactions + community_interactions_attachments + community_interactions_analysis_topics
  • community_templates + community_templates_locales + community_templates_variables + community_templates_auto_suggest_keywords
  • community_rules + community_rules_trigger_keywords + community_rules_actions
  • payload_locked_documents_rels erweitern um alle neuen Collection-IDs

WICHTIG: Siehe CLAUDE.md Abschnitt "KRITISCH: Neue Collections hinzufügen" für das korrekte Migrations-Muster.


Zusammenfassung

Neue Collections (5)

  1. social-platforms Plattform-Definitionen
  2. social-accounts Account-Verknüpfungen
  3. community-interactions Alle Kommentare/DMs
  4. community-templates Antwort-Vorlagen
  5. community-rules Auto-Regeln

Neue Lib-Dateien (3)

  1. src/lib/communityAccess.ts Access Control
  2. src/lib/integrations/youtube/YouTubeClient.ts YouTube API
  3. src/lib/integrations/claude/ClaudeAnalysisService.ts AI Analyse

API Endpoints (2)

  1. POST /api/community/sync-comments Manueller Sync
  2. POST /api/community/reply Antwort senden

User-Feld (1)

  1. communityRole in Users Collection

Abhängigkeiten zu bestehendem Code

  • youtube-channels Collection (linkedChannel Referenz)
  • youtube-content Collection (linkedContent Referenz)
  • users Collection (assignedTo, sentBy Referenzen)
  • Bestehende YouTube-Access-Rollen werden berücksichtigt