cms.c2sgmbh/src/app/(payload)/api/cron/community-sync/route.ts
Martin Porwoll 89882a545b feat(community): Phase 2.4 - Unified Sync Service
Implements a unified sync service that orchestrates comment
synchronization across all social media platforms.

UnifiedSyncService:
- Platform-agnostic sync orchestration
- Support for YouTube, Facebook, and Instagram
- Parallel platform detection and account grouping
- Progress tracking with live status updates
- Aggregated results per platform
- Error handling with partial results support

New API Endpoints:
- GET/POST /api/cron/community-sync
  - Cron endpoint for scheduled multi-platform sync
  - Query params: platforms, accountIds, analyzeWithAI, maxItems
  - HEAD for monitoring status

- GET /api/community/sync-status
  - Live sync status for dashboard
  - Platform overview with account details
  - Interaction statistics (total, today, unanswered)
  - Last sync result summary

Configuration:
- vercel.json updated to use community-sync cron
- 15-minute sync interval maintained

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 21:33:42 +00:00

203 lines
5.8 KiB
TypeScript

// src/app/(payload)/api/cron/community-sync/route.ts
// Unified Community Sync Cron Endpoint
import { NextRequest, NextResponse } from 'next/server'
import {
runUnifiedSync,
getUnifiedSyncStatus,
type SupportedPlatform,
type UnifiedSyncOptions,
} from '@/lib/jobs/UnifiedSyncService'
// Geheimer Token für Cron-Authentifizierung
const CRON_SECRET = process.env.CRON_SECRET
/**
* GET /api/cron/community-sync
* Wird von externem Cron-Job aufgerufen (z.B. Vercel Cron, cron-job.org)
*
* Query Parameters:
* - platforms: Komma-separierte Liste (youtube,facebook,instagram)
* - accountIds: Komma-separierte Account-IDs
* - analyzeWithAI: true/false (default: true)
* - maxItems: Maximale Items pro Account (default: 100)
*/
export async function GET(request: NextRequest) {
// Auth prüfen wenn CRON_SECRET gesetzt
if (CRON_SECRET) {
const authHeader = request.headers.get('authorization')
if (authHeader !== `Bearer ${CRON_SECRET}`) {
console.warn('[Cron] Unauthorized request to community-sync')
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
}
// Query-Parameter parsen
const searchParams = request.nextUrl.searchParams
const options: UnifiedSyncOptions = {}
// Plattform-Filter
const platformsParam = searchParams.get('platforms')
if (platformsParam) {
const platforms = platformsParam.split(',').map((p) => p.trim().toLowerCase())
const validPlatforms = platforms.filter((p): p is SupportedPlatform =>
['youtube', 'facebook', 'instagram'].includes(p)
)
if (validPlatforms.length > 0) {
options.platforms = validPlatforms
}
}
// Account-ID Filter
const accountIdsParam = searchParams.get('accountIds')
if (accountIdsParam) {
const ids = accountIdsParam
.split(',')
.map((id) => parseInt(id.trim(), 10))
.filter((id) => !isNaN(id))
if (ids.length > 0) {
options.accountIds = ids
}
}
// AI-Analyse
const analyzeParam = searchParams.get('analyzeWithAI')
if (analyzeParam !== null) {
options.analyzeWithAI = analyzeParam !== 'false'
}
// Max Items
const maxItemsParam = searchParams.get('maxItems')
if (maxItemsParam) {
const maxItems = parseInt(maxItemsParam, 10)
if (!isNaN(maxItems) && maxItems > 0) {
options.maxItemsPerAccount = maxItems
}
}
console.log('[Cron] Starting community sync', { options })
const result = await runUnifiedSync(options)
if (result.success) {
// Gesamtstatistiken berechnen
let totalNew = 0
let totalUpdated = 0
let totalErrors = 0
for (const platform of Object.values(result.platforms)) {
if (platform) {
totalNew += platform.totalNewComments
totalUpdated += platform.totalUpdatedComments
totalErrors += platform.totalErrors
}
}
return NextResponse.json({
success: true,
message: `Sync completed: ${totalNew} new, ${totalUpdated} updated comments across ${result.results.length} accounts`,
duration: result.duration,
platforms: result.platforms,
results: result.results,
})
}
return NextResponse.json(
{
success: false,
errors: result.errors,
partialResults: result.results,
},
{ status: 500 }
)
}
/**
* POST /api/cron/community-sync
* Manueller Sync-Trigger mit erweiterten Optionen
*/
export async function POST(request: NextRequest) {
// Auth prüfen
if (CRON_SECRET) {
const authHeader = request.headers.get('authorization')
if (authHeader !== `Bearer ${CRON_SECRET}`) {
console.warn('[Cron] Unauthorized POST to community-sync')
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
}
try {
const body = await request.json()
const options: UnifiedSyncOptions = {}
// Optionen aus Body übernehmen
if (body.platforms && Array.isArray(body.platforms)) {
options.platforms = body.platforms.filter((p: string): p is SupportedPlatform =>
['youtube', 'facebook', 'instagram'].includes(p)
)
}
if (body.accountIds && Array.isArray(body.accountIds)) {
options.accountIds = body.accountIds.filter(
(id: unknown) => typeof id === 'number' && id > 0
)
}
if (typeof body.analyzeWithAI === 'boolean') {
options.analyzeWithAI = body.analyzeWithAI
}
if (typeof body.maxItemsPerAccount === 'number' && body.maxItemsPerAccount > 0) {
options.maxItemsPerAccount = body.maxItemsPerAccount
}
if (body.sinceDate) {
const date = new Date(body.sinceDate)
if (!isNaN(date.getTime())) {
options.sinceDate = date
}
}
console.log('[Cron] Manual community sync triggered', { options })
const result = await runUnifiedSync(options)
return NextResponse.json({
success: result.success,
startedAt: result.startedAt,
completedAt: result.completedAt,
duration: result.duration,
platforms: result.platforms,
results: result.results,
errors: result.errors,
})
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error'
console.error('[Cron] POST error:', error)
return NextResponse.json({ error: message }, { status: 500 })
}
}
/**
* HEAD /api/cron/community-sync
* Status-Check für Monitoring
*/
export async function HEAD() {
const status = getUnifiedSyncStatus()
return new NextResponse(null, {
status: status.isRunning ? 423 : 200, // 423 = Locked (running)
headers: {
'X-Sync-Running': status.isRunning.toString(),
'X-Current-Platform': status.currentPlatform || 'none',
'X-Current-Account': status.currentAccount || 'none',
'X-Progress': `${status.progress.percentage}%`,
'X-Last-Run': status.lastRunAt || 'never',
},
})
}
export const dynamic = 'force-dynamic'
export const maxDuration = 300 // 5 Minuten max (Vercel)