mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 18:34:13 +00:00
Community Management Phase 1 completion: - Add Community Inbox admin view with filters, stats, and reply functionality - Add Rules Engine service for automated interaction processing - Add YouTube OAuth flow (auth, callback, token refresh) - Add Comment Sync cron job (every 15 minutes) - Add Community Export API (PDF/Excel/CSV) - Fix database schema for community_rules hasMany fields - Fix access control in communityAccess.ts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
567 lines
14 KiB
TypeScript
567 lines
14 KiB
TypeScript
// src/collections/CommunityInteractions.ts
|
|
|
|
import type { CollectionConfig } from 'payload'
|
|
import {
|
|
isCommunityModeratorOrAbove,
|
|
isCommunityManager,
|
|
canAccessAssignedInteractions,
|
|
} from '../lib/communityAccess'
|
|
|
|
/**
|
|
* CommunityInteractions Collection
|
|
*
|
|
* Speichert alle Social Media Interaktionen (Kommentare, DMs, Mentions).
|
|
* Teil des Community Management Systems.
|
|
*/
|
|
export const CommunityInteractions: CollectionConfig = {
|
|
slug: 'community-interactions',
|
|
labels: {
|
|
singular: 'Interaction',
|
|
plural: 'Interactions',
|
|
},
|
|
admin: {
|
|
group: 'Community',
|
|
defaultColumns: ['platform', 'type', 'author.name', '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 via YtNotifications
|
|
console.log(`🚨 Urgent interaction: ${doc.id}`)
|
|
}
|
|
},
|
|
// Run Rules Engine on new interactions
|
|
async ({ doc, operation, req }) => {
|
|
if (operation === 'create') {
|
|
try {
|
|
const { RulesEngine } = await import('@/lib/services/RulesEngine')
|
|
const rulesEngine = new RulesEngine(req.payload)
|
|
const result = await rulesEngine.evaluateRules(doc.id)
|
|
if (result.appliedRules.length > 0) {
|
|
console.log(`[RulesEngine] Applied ${result.appliedRules.length} rules to interaction ${doc.id}:`, result.appliedRules)
|
|
}
|
|
} catch (error) {
|
|
console.error('[RulesEngine] Error evaluating rules:', error)
|
|
}
|
|
}
|
|
},
|
|
],
|
|
},
|
|
timestamps: true,
|
|
}
|